Skip to content

Commit 65fc1bc

Browse files
authored
🎉 Source Github: IssueReactions - re-implemented using GraphQL (#21457)
Signed-off-by: Sergey Chvalyuk <[email protected]>
1 parent 0bf9f19 commit 65fc1bc

File tree

10 files changed

+116
-17
lines changed

10 files changed

+116
-17
lines changed

airbyte-config/init/src/main/resources/seed/source_definitions.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@
597597
- name: GitHub
598598
sourceDefinitionId: ef69ef6e-aa7f-4af1-a01d-ef775033524e
599599
dockerRepository: airbyte/source-github
600-
dockerImageTag: 0.3.11
600+
dockerImageTag: 0.4.0
601601
documentationUrl: https://docs.airbyte.com/integrations/sources/github
602602
icon: github.svg
603603
sourceType: api

airbyte-config/init/src/main/resources/seed/source_specs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4705,7 +4705,7 @@
47054705
supportsNormalization: false
47064706
supportsDBT: false
47074707
supported_destination_sync_modes: []
4708-
- dockerImage: "airbyte/source-github:0.3.11"
4708+
- dockerImage: "airbyte/source-github:0.4.0"
47094709
spec:
47104710
documentationUrl: "https://docs.airbyte.com/integrations/sources/github"
47114711
connectionSpecification:

airbyte-integrations/connectors/source-github/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ RUN pip install .
1212
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
1313
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]
1414

15-
LABEL io.airbyte.version=0.3.12
15+
LABEL io.airbyte.version=0.4.0
1616
LABEL io.airbyte.name=airbyte/source-github

airbyte-integrations/connectors/source-github/acceptance-test-config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ acceptance_tests:
4747
["airbytehq/integration-test", "907296275", "created_at"]
4848
issue_events: ["airbytehq/integration-test", "created_at"]
4949
issue_milestones: ["airbytehq/integration-test", "updated_at"]
50-
issue_reactions: ["airbytehq/integration-test", "11", "created_at"]
50+
issue_reactions: ["airbytehq/integration-test", "created_at"]
5151
issues: ["airbytehq/integration-test", "updated_at"]
5252
project_cards:
5353
["airbytehq/integration-test", "13167124", "17807006", "updated_at"]

airbyte-integrations/connectors/source-github/integration_tests/abnormal_state.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@
8989
"type": "STREAM",
9090
"stream": {
9191
"stream_state": {
92-
"airbytehq/integration-test": {
93-
"11": { "created_at": "2121-12-31T23:59:59Z" }
94-
}
92+
"airbytehq/integration-test": { "created_at": "2121-12-31T23:59:59Z" }
9593
},
9694
"stream_descriptor": { "name": "issue_reactions" }
9795
}

airbyte-integrations/connectors/source-github/integration_tests/expected_records.jsonl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
{"stream":"issue_labels","data":{"id":3300346197,"node_id":"MDU6TGFiZWwzMzAwMzQ2MTk3","url":"https://api.github.com/repos/airbytehq/integration-test/labels/critical","name":"critical","color":"ededed","default":false,"description":null,"repository":"airbytehq/integration-test"},"emitted_at":1673559178152}
4747
{"stream":"issue_milestones","data":{"url":"https://api.github.com/repos/airbytehq/integration-test/milestones/4","html_url":"https://github.com/airbytehq/integration-test/milestone/4","labels_url":"https://api.github.com/repos/airbytehq/integration-test/milestones/4/labels","id":7786502,"node_id":"MI_kwDOF9hP9c4AdtAG","number":4,"title":"m2","description":"","creator":{"login":"airbyteio","id":92915184,"node_id":"U_kgDOBYnF8A","avatar_url":"https://avatars.githubusercontent.com/u/92915184?v=4","gravatar_id":"","url":"https://api.github.com/users/airbyteio","html_url":"https://github.com/airbyteio","followers_url":"https://api.github.com/users/airbyteio/followers","following_url":"https://api.github.com/users/airbyteio/following{/other_user}","gists_url":"https://api.github.com/users/airbyteio/gists{/gist_id}","starred_url":"https://api.github.com/users/airbyteio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/airbyteio/subscriptions","organizations_url":"https://api.github.com/users/airbyteio/orgs","repos_url":"https://api.github.com/users/airbyteio/repos","events_url":"https://api.github.com/users/airbyteio/events{/privacy}","received_events_url":"https://api.github.com/users/airbyteio/received_events","type":"User","site_admin":false},"open_issues":0,"closed_issues":0,"state":"open","created_at":"2022-03-21T08:37:46Z","updated_at":"2022-03-21T08:37:46Z","due_on":null,"closed_at":null,"repository":"airbytehq/integration-test"},"emitted_at":1673559253517}
4848
{"stream":"issue_milestones","data":{"url":"https://api.github.com/repos/airbytehq/integration-test/milestones/1","html_url":"https://github.com/airbytehq/integration-test/milestone/1","labels_url":"https://api.github.com/repos/airbytehq/integration-test/milestones/1/labels","id":7097357,"node_id":"MI_kwDOF9hP9c4AbEwN","number":1,"title":"main","description":null,"creator":{"login":"gaart","id":743901,"node_id":"MDQ6VXNlcjc0MzkwMQ==","avatar_url":"https://avatars.githubusercontent.com/u/743901?v=4","gravatar_id":"","url":"https://api.github.com/users/gaart","html_url":"https://github.com/gaart","followers_url":"https://api.github.com/users/gaart/followers","following_url":"https://api.github.com/users/gaart/following{/other_user}","gists_url":"https://api.github.com/users/gaart/gists{/gist_id}","starred_url":"https://api.github.com/users/gaart/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gaart/subscriptions","organizations_url":"https://api.github.com/users/gaart/orgs","repos_url":"https://api.github.com/users/gaart/repos","events_url":"https://api.github.com/users/gaart/events{/privacy}","received_events_url":"https://api.github.com/users/gaart/received_events","type":"User","site_admin":false},"open_issues":3,"closed_issues":1,"state":"open","created_at":"2021-08-27T15:43:44Z","updated_at":"2021-08-27T16:02:49Z","due_on":null,"closed_at":null,"repository":"airbytehq/integration-test"},"emitted_at":1673559253517}
49-
{"stream":"issue_reactions","data":{"id":127048454,"node_id":"MDEzOklzc3VlUmVhY3Rpb24xMjcwNDg0NTQ=","user":{"login":"yevhenii-ldv","id":34103125,"node_id":"MDQ6VXNlcjM0MTAzMTI1","avatar_url":"https://avatars.githubusercontent.com/u/34103125?u=3e49bb73177a9f70896e3d49b34656ab659c70a5&v=4","gravatar_id":"","url":"https://api.github.com/users/yevhenii-ldv","html_url":"https://github.com/yevhenii-ldv","followers_url":"https://api.github.com/users/yevhenii-ldv/followers","following_url":"https://api.github.com/users/yevhenii-ldv/following{/other_user}","gists_url":"https://api.github.com/users/yevhenii-ldv/gists{/gist_id}","starred_url":"https://api.github.com/users/yevhenii-ldv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yevhenii-ldv/subscriptions","organizations_url":"https://api.github.com/users/yevhenii-ldv/orgs","repos_url":"https://api.github.com/users/yevhenii-ldv/repos","events_url":"https://api.github.com/users/yevhenii-ldv/events{/privacy}","received_events_url":"https://api.github.com/users/yevhenii-ldv/received_events","type":"User","site_admin":false},"content":"eyes","created_at":"2021-09-06T11:13:31Z","repository":"airbytehq/integration-test","issue_number":11},"emitted_at":1673559327732}
50-
{"stream":"issue_reactions","data":{"id":127048456,"node_id":"MDEzOklzc3VlUmVhY3Rpb24xMjcwNDg0NTY=","user":{"login":"yevhenii-ldv","id":34103125,"node_id":"MDQ6VXNlcjM0MTAzMTI1","avatar_url":"https://avatars.githubusercontent.com/u/34103125?u=3e49bb73177a9f70896e3d49b34656ab659c70a5&v=4","gravatar_id":"","url":"https://api.github.com/users/yevhenii-ldv","html_url":"https://github.com/yevhenii-ldv","followers_url":"https://api.github.com/users/yevhenii-ldv/followers","following_url":"https://api.github.com/users/yevhenii-ldv/following{/other_user}","gists_url":"https://api.github.com/users/yevhenii-ldv/gists{/gist_id}","starred_url":"https://api.github.com/users/yevhenii-ldv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yevhenii-ldv/subscriptions","organizations_url":"https://api.github.com/users/yevhenii-ldv/orgs","repos_url":"https://api.github.com/users/yevhenii-ldv/repos","events_url":"https://api.github.com/users/yevhenii-ldv/events{/privacy}","received_events_url":"https://api.github.com/users/yevhenii-ldv/received_events","type":"User","site_admin":false},"content":"rocket","created_at":"2021-09-06T11:13:32Z","repository":"airbytehq/integration-test","issue_number":11},"emitted_at":1673559327732}
49+
{"stream":"issue_reactions","data":{"node_id":"MDEzOklzc3VlUmVhY3Rpb24xMjcwNDg0NTQ=","id":127048454,"content":"EYES","created_at":"2021-09-06T11:13:31Z","user":{"node_id":"MDQ6VXNlcjM0MTAzMTI1","id":34103125,"login":"yevhenii-ldv","avatar_url":"https://avatars.githubusercontent.com/u/34103125?u=3e49bb73177a9f70896e3d49b34656ab659c70a5&v=4","html_url":"https://github.com/yevhenii-ldv","site_admin":false,"type":"User"},"repository":"airbytehq/integration-test","issue_number":11},"emitted_at":1674174073303}
50+
{"stream":"issue_reactions","data":{"node_id":"MDEzOklzc3VlUmVhY3Rpb24xMjcwNDg0NTY=","id":127048456,"content":"ROCKET","created_at":"2021-09-06T11:13:32Z","user":{"node_id":"MDQ6VXNlcjM0MTAzMTI1","id":34103125,"login":"yevhenii-ldv","avatar_url":"https://avatars.githubusercontent.com/u/34103125?u=3e49bb73177a9f70896e3d49b34656ab659c70a5&v=4","html_url":"https://github.com/yevhenii-ldv","site_admin":false,"type":"User"},"repository":"airbytehq/integration-test","issue_number":11},"emitted_at":1674174073303}
5151
{"stream":"issue_comment_reactions","data":{"id":127042034,"node_id":"MDIwOklzc3VlQ29tbWVudFJlYWN0aW9uMTI3MDQyMDM0","user":{"login":"yevhenii-ldv","id":34103125,"node_id":"MDQ6VXNlcjM0MTAzMTI1","avatar_url":"https://avatars.githubusercontent.com/u/34103125?v=4","gravatar_id":"","url":"https://api.github.com/users/yevhenii-ldv","html_url":"https://github.com/yevhenii-ldv","followers_url":"https://api.github.com/users/yevhenii-ldv/followers","following_url":"https://api.github.com/users/yevhenii-ldv/following{/other_user}","gists_url":"https://api.github.com/users/yevhenii-ldv/gists{/gist_id}","starred_url":"https://api.github.com/users/yevhenii-ldv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yevhenii-ldv/subscriptions","organizations_url":"https://api.github.com/users/yevhenii-ldv/orgs","repos_url":"https://api.github.com/users/yevhenii-ldv/repos","events_url":"https://api.github.com/users/yevhenii-ldv/events{/privacy}","received_events_url":"https://api.github.com/users/yevhenii-ldv/received_events","type":"User","site_admin":false},"content":"hooray","created_at":"2021-09-06T10:18:19Z","repository":"airbytehq/integration-test","comment_id":907296275},"emitted_at":1673559403000}
5252
{"stream":"issue_comment_reactions","data":{"id":127042037,"node_id":"MDIwOklzc3VlQ29tbWVudFJlYWN0aW9uMTI3MDQyMDM3","user":{"login":"yevhenii-ldv","id":34103125,"node_id":"MDQ6VXNlcjM0MTAzMTI1","avatar_url":"https://avatars.githubusercontent.com/u/34103125?v=4","gravatar_id":"","url":"https://api.github.com/users/yevhenii-ldv","html_url":"https://github.com/yevhenii-ldv","followers_url":"https://api.github.com/users/yevhenii-ldv/followers","following_url":"https://api.github.com/users/yevhenii-ldv/following{/other_user}","gists_url":"https://api.github.com/users/yevhenii-ldv/gists{/gist_id}","starred_url":"https://api.github.com/users/yevhenii-ldv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yevhenii-ldv/subscriptions","organizations_url":"https://api.github.com/users/yevhenii-ldv/orgs","repos_url":"https://api.github.com/users/yevhenii-ldv/repos","events_url":"https://api.github.com/users/yevhenii-ldv/events{/privacy}","received_events_url":"https://api.github.com/users/yevhenii-ldv/received_events","type":"User","site_admin":false},"content":"laugh","created_at":"2021-09-06T10:18:20Z","repository":"airbytehq/integration-test","comment_id":907296275},"emitted_at":1673559403000}
5353
{"stream":"commit_comment_reactions","data":{"id":154935456,"node_id":"REA_lADOF9hP9c4DT3SJzgk8IKA","user":{"login":"grubberr","id":195743,"node_id":"MDQ6VXNlcjE5NTc0Mw==","avatar_url":"https://avatars.githubusercontent.com/u/195743?v=4","gravatar_id":"","url":"https://api.github.com/users/grubberr","html_url":"https://github.com/grubberr","followers_url":"https://api.github.com/users/grubberr/followers","following_url":"https://api.github.com/users/grubberr/following{/other_user}","gists_url":"https://api.github.com/users/grubberr/gists{/gist_id}","starred_url":"https://api.github.com/users/grubberr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grubberr/subscriptions","organizations_url":"https://api.github.com/users/grubberr/orgs","repos_url":"https://api.github.com/users/grubberr/repos","events_url":"https://api.github.com/users/grubberr/events{/privacy}","received_events_url":"https://api.github.com/users/grubberr/received_events","type":"User","site_admin":false},"content":"heart","created_at":"2022-03-20T11:30:23Z","repository":"airbytehq/integration-test","comment_id":55538825},"emitted_at":1673560627726}

airbyte-integrations/connectors/source-github/source_github/graphql.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,37 @@ def get_query_reviews(owner, name, first, after, number=None):
9999
return str(op)
100100

101101

102+
def get_query_issue_reactions(owner, name, first, after, number=None):
103+
op = sgqlc.operation.Operation(_schema_root.query_type)
104+
repository = op.repository(owner=owner, name=name)
105+
repository.name()
106+
repository.owner.login()
107+
if number:
108+
issue = repository.issue(number=number)
109+
else:
110+
kwargs = {"first": first}
111+
if after:
112+
kwargs["after"] = after
113+
issues = repository.issues(**kwargs)
114+
issues.page_info.__fields__(has_next_page=True, end_cursor=True)
115+
issue = issues.nodes
116+
117+
issue.__fields__(number=True)
118+
kwargs = {"first": first}
119+
if number and after:
120+
kwargs["after"] = after
121+
reactions = issue.reactions(**kwargs)
122+
reactions.page_info.__fields__(has_next_page=True, end_cursor=True)
123+
reactions.nodes.__fields__(
124+
id="node_id",
125+
database_id="id",
126+
content=True,
127+
created_at="created_at",
128+
)
129+
select_user_fields(reactions.nodes.user())
130+
return str(op)
131+
132+
102133
class QueryReactions:
103134

104135
# AVERAGE_REVIEWS - optimal number of reviews to fetch inside every pull request.

airbyte-integrations/connectors/source-github/source_github/schemas/issue_reactions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"format": "date-time"
1717
},
1818
"user": {
19-
"$ref": "user.json"
19+
"$ref": "user_graphql.json"
2020
},
2121
"repository": {
2222
"type": "string"

airbyte-integrations/connectors/source-github/source_github/streams.py

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from airbyte_cdk.sources.streams.http.exceptions import DefaultBackoffException
1515
from requests.exceptions import HTTPError
1616

17-
from .graphql import CursorStorage, QueryReactions, get_query_pull_requests, get_query_reviews
17+
from .graphql import CursorStorage, QueryReactions, get_query_issue_reactions, get_query_pull_requests, get_query_reviews
1818
from .utils import getter
1919

2020
DEFAULT_PAGE_SIZE = 100
@@ -1002,14 +1002,83 @@ class IssueCommentReactions(ReactionStream):
10021002
parent_entity = Comments
10031003

10041004

1005-
class IssueReactions(ReactionStream):
1005+
class IssueReactions(SemiIncrementalMixin, GithubStream):
10061006
"""
1007-
API docs: https://docs.github.com/en/rest/reference/reactions#list-reactions-for-an-issue
1007+
https://docs.github.com/en/graphql/reference/objects#issue
1008+
https://docs.github.com/en/graphql/reference/objects#reaction
10081009
"""
10091010

1010-
parent_entity = Issues
1011-
parent_key = "number"
1012-
copy_parent_key = "issue_number"
1011+
http_method = "POST"
1012+
cursor_field = "created_at"
1013+
1014+
def __init__(self, **kwargs):
1015+
super().__init__(**kwargs)
1016+
self.issues_cursor = {}
1017+
self.reactions_cursors = {}
1018+
1019+
def path(
1020+
self, *, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
1021+
) -> str:
1022+
return "graphql"
1023+
1024+
def raise_error_from_response(self, response_json):
1025+
if "errors" in response_json:
1026+
raise Exception(str(response_json["errors"]))
1027+
1028+
def _get_name(self, repository):
1029+
return repository["owner"]["login"] + "/" + repository["name"]
1030+
1031+
def _get_reactions_from_issue(self, issue, repository_name):
1032+
for reaction in issue["reactions"]["nodes"]:
1033+
reaction["repository"] = repository_name
1034+
reaction["issue_number"] = issue["number"]
1035+
reaction["user"]["type"] = "User"
1036+
yield reaction
1037+
1038+
def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]:
1039+
self.raise_error_from_response(response_json=response.json())
1040+
repository = response.json()["data"]["repository"]
1041+
if repository:
1042+
repository_name = self._get_name(repository)
1043+
if "issues" in repository:
1044+
for issue in repository["issues"]["nodes"]:
1045+
yield from self._get_reactions_from_issue(issue, repository_name)
1046+
elif "issue" in repository:
1047+
yield from self._get_reactions_from_issue(repository["issue"], repository_name)
1048+
1049+
def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]:
1050+
repository = response.json()["data"]["repository"]
1051+
if repository:
1052+
repository_name = self._get_name(repository)
1053+
reactions_cursors = self.reactions_cursors.setdefault(repository_name, {})
1054+
if "issues" in repository:
1055+
if repository["issues"]["pageInfo"]["hasNextPage"]:
1056+
self.issues_cursor[repository_name] = repository["issues"]["pageInfo"]["endCursor"]
1057+
for issue in repository["issues"]["nodes"]:
1058+
if issue["reactions"]["pageInfo"]["hasNextPage"]:
1059+
issue_number = issue["number"]
1060+
reactions_cursors[issue_number] = issue["reactions"]["pageInfo"]["endCursor"]
1061+
elif "issue" in repository:
1062+
if repository["issue"]["reactions"]["pageInfo"]["hasNextPage"]:
1063+
issue_number = repository["issue"]["number"]
1064+
reactions_cursors[issue_number] = repository["issue"]["reactions"]["pageInfo"]["endCursor"]
1065+
if reactions_cursors:
1066+
number, after = reactions_cursors.popitem()
1067+
return {"after": after, "number": number}
1068+
if repository_name in self.issues_cursor:
1069+
return {"after": self.issues_cursor.pop(repository_name)}
1070+
1071+
def request_body_json(
1072+
self,
1073+
stream_state: Mapping[str, Any],
1074+
stream_slice: Mapping[str, Any] = None,
1075+
next_page_token: Mapping[str, Any] = None,
1076+
) -> Optional[Mapping]:
1077+
organization, name = stream_slice["repository"].split("/")
1078+
if not next_page_token:
1079+
next_page_token = {"after": None}
1080+
query = get_query_issue_reactions(owner=organization, name=name, first=self.page_size, **next_page_token)
1081+
return {"query": query}
10131082

10141083

10151084
class PullRequestCommentReactions(SemiIncrementalMixin, GithubStream):

0 commit comments

Comments
 (0)