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
@@ -0,0 +1,339 @@
1
+ #
2
+ # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
+ #
4
+
5
+ from os import getenv
6
+ from typing import Any, Dict, List, Mapping, MutableMapping, Tuple
7
+ from urllib.parse import urlparse
8
+
9
+ from airbyte_cdk import AirbyteLogger
10
+ from airbyte_cdk.models import FailureType, SyncMode
11
+ from airbyte_cdk.sources import AbstractSource
12
+ from airbyte_cdk.sources.streams import Stream
13
+ from airbyte_cdk.sources.streams.http.auth import MultipleTokenAuthenticator
14
+ from airbyte_cdk.utils.traced_exception import AirbyteTracedException
15
+ from source_github.utils import MultipleTokenAuthenticatorWithRateLimiter
16
+
17
+ from . import constants
18
+ from .streams import (
19
+ Assignees,
20
+ Branches,
21
+ Collaborators,
22
+ Comments,
23
+ CommitCommentReactions,
24
+ CommitComments,
25
+ Commits,
26
+ ContributorActivity,
27
+ Deployments,
28
+ Events,
29
+ IssueCommentReactions,
30
+ IssueEvents,
31
+ IssueLabels,
32
+ IssueMilestones,
33
+ IssueReactions,
34
+ Issues,
35
+ IssueTimelineEvents,
36
+ Organizations,
37
+ ProjectCards,
38
+ ProjectColumns,
39
+ Projects,
40
+ ProjectsV2,
41
+ PullRequestCommentReactions,
42
+ PullRequestCommits,
43
+ PullRequests,
44
+ PullRequestStats,
45
+ Releases,
46
+ Repositories,
47
+ RepositoryStats,
48
+ ReviewComments,
49
+ Reviews,
50
+ Stargazers,
51
+ Tags,
52
+ TeamMembers,
53
+ TeamMemberships,
54
+ Teams,
55
+ Users,
56
+ WorkflowJobs,
57
+ WorkflowRuns,
58
+ Workflows,
59
+ )
60
+ from .utils import read_full_refresh
61
+
62
+
63
+ class SourceGithub(AbstractSource):
64
+ @staticmethod
65
+ def _get_org_repositories(config: Mapping[str, Any], authenticator: MultipleTokenAuthenticator) -> Tuple[List[str], List[str]]:
66
+ """
67
+ Parse config/repositories and produce two lists: organizations, repositories.
68
+ Args:
69
+ config (dict): Dict representing connector's config
70
+ authenticator(MultipleTokenAuthenticator): authenticator object
71
+ """
72
+ config_repositories = set(config.get("repositories"))
73
+
74
+ repositories = set()
75
+ organizations = set()
76
+ unchecked_repos = set()
77
+ unchecked_orgs = set()
78
+
79
+ for org_repos in config_repositories:
80
+ org, _, repos = org_repos.partition("/")
81
+ if repos == "*":
82
+ unchecked_orgs.add(org)
83
+ else:
84
+ unchecked_repos.add(org_repos)
85
+
86
+ if unchecked_orgs:
87
+ stream = Repositories(authenticator=authenticator, organizations=unchecked_orgs, api_url=config.get("api_url"))
88
+ for record in read_full_refresh(stream):
89
+ repositories.add(record["full_name"])
90
+ organizations.add(record["organization"])
91
+
92
+ unchecked_repos = unchecked_repos - repositories
93
+ if unchecked_repos:
94
+ stream = RepositoryStats(
95
+ authenticator=authenticator,
96
+ repositories=unchecked_repos,
97
+ api_url=config.get("api_url"),
98
+ # This parameter is deprecated and in future will be used sane default, page_size: 10
99
+ page_size_for_large_streams=config.get("page_size_for_large_streams", constants.DEFAULT_PAGE_SIZE_FOR_LARGE_STREAM),
100
+ )
101
+ for record in read_full_refresh(stream):
102
+ repositories.add(record["full_name"])
103
+ organization = record.get("organization", {}).get("login")
104
+ if organization:
105
+ organizations.add(organization)
106
+
107
+ return list(organizations), list(repositories)
108
+
109
+ @staticmethod
110
+ def get_access_token(config: Mapping[str, Any]):
111
+ # Before we supported oauth, personal_access_token was called `access_token` and it lived at the
112
+ # config root. So we first check to make sure any backwards compatbility is handled.
113
+ if "access_token" in config:
114
+ return constants.PERSONAL_ACCESS_TOKEN_TITLE, config["access_token"]
115
+
116
+ credentials = config.get("credentials", {})
117
+ if "access_token" in credentials:
118
+ return constants.ACCESS_TOKEN_TITLE, credentials["access_token"]
119
+ if "personal_access_token" in credentials:
120
+ return constants.PERSONAL_ACCESS_TOKEN_TITLE, credentials["personal_access_token"]
121
+ raise Exception("Invalid config format")
122
+
123
+ def _get_authenticator(self, config: Mapping[str, Any]):
124
+ _, token = self.get_access_token(config)
125
+ tokens = [t.strip() for t in token.split(constants.TOKEN_SEPARATOR)]
126
+ return MultipleTokenAuthenticatorWithRateLimiter(tokens=tokens)
127
+
128
+ def _validate_and_transform_config(self, config: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
129
+ config = self._ensure_default_values(config)
130
+ config = self._validate_repositories(config)
131
+ config = self._validate_branches(config)
132
+ return config
133
+
134
+ def _ensure_default_values(self, config: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
135
+ config.setdefault("api_url", "https://api.github.com")
136
+ api_url_parsed = urlparse(config["api_url"])
137
+
138
+ if not api_url_parsed.scheme.startswith("http"):
139
+ message = "Please enter a full url for `API URL` field starting with `http`"
140
+ elif api_url_parsed.scheme == "http" and not self._is_http_allowed():
141
+ message = "HTTP connection is insecure and is not allowed in this environment. Please use `https` instead."
142
+ elif not api_url_parsed.netloc:
143
+ message = "Please provide a correct API URL."
144
+ else:
145
+ return config
146
+
147
+ raise AirbyteTracedException(message=message, failure_type=FailureType.config_error)
148
+
149
+ def _validate_repositories(self, config: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
150
+ if config.get("repositories"):
151
+ pass
152
+ elif config.get("repository"):
153
+ config["repositories"] = set(filter(None, config["repository"].split(" ")))
154
+
155
+ return config
156
+
157
+ def _validate_branches(self, config: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
158
+ if config.get("branches"):
159
+ pass
160
+ elif config.get("branch"):
161
+ config["branches"] = set(filter(None, config["branch"].split(" ")))
162
+
163
+ return config
164
+
165
+ @staticmethod
166
+ def _is_http_allowed() -> bool:
167
+ return getenv("DEPLOYMENT_MODE", "").upper() != "CLOUD"
168
+
169
+ @staticmethod
170
+ def _get_branches_data(
171
+ selected_branches: List, full_refresh_args: Dict[str, Any] = None
172
+ ) -> Tuple[Dict[str, str], Dict[str, List[str]]]:
173
+ selected_branches = set(selected_branches)
174
+
175
+ # Get the default branch for each repository
176
+ default_branches = {}
177
+ repository_stats_stream = RepositoryStats(**full_refresh_args)
178
+ for stream_slice in repository_stats_stream.stream_slices(sync_mode=SyncMode.full_refresh):
179
+ default_branches.update(
180
+ {
181
+ repo_stats["full_name"]: repo_stats["default_branch"]
182
+ for repo_stats in repository_stats_stream.read_records(sync_mode=SyncMode.full_refresh, stream_slice=stream_slice)
183
+ }
184
+ )
185
+
186
+ all_branches = []
187
+ branches_stream = Branches(**full_refresh_args)
188
+ for stream_slice in branches_stream.stream_slices(sync_mode=SyncMode.full_refresh):
189
+ for branch in branches_stream.read_records(sync_mode=SyncMode.full_refresh, stream_slice=stream_slice):
190
+ all_branches.append(f"{branch['repository']}/{branch['name']}")
191
+
192
+ # Create mapping of repository to list of branches to pull commits for
193
+ # If no branches are specified for a repo, use its default branch
194
+ branches_to_pull: Dict[str, List[str]] = {}
195
+ for repo in full_refresh_args["repositories"]:
196
+ repo_branches = []
197
+ for branch in selected_branches:
198
+ branch_parts = branch.split("/", 2)
199
+ if "/".join(branch_parts[:2]) == repo and branch in all_branches:
200
+ repo_branches.append(branch_parts[-1])
201
+ if not repo_branches:
202
+ repo_branches = [default_branches[repo]]
203
+
204
+ branches_to_pull[repo] = repo_branches
205
+
206
+ return default_branches, branches_to_pull
207
+
208
+ def user_friendly_error_message(self, message: str) -> str:
209
+ user_message = ""
210
+ if "404 Client Error: Not Found for url: https://api.github.com/repos/" in message:
211
+ # 404 Client Error: Not Found for url: https://api.github.com/repos/airbytehq/airbyte3?per_page=100
212
+ full_repo_name = message.split("https://api.github.com/repos/")[1].split("?")[0]
213
+ user_message = f'Repo name: "{full_repo_name}" is unknown, "repository" config option should use existing full repo name <organization>/<repository>'
214
+ elif "404 Client Error: Not Found for url: https://api.github.com/orgs/" in message:
215
+ # 404 Client Error: Not Found for url: https://api.github.com/orgs/airbytehqBLA/repos?per_page=100
216
+ org_name = message.split("https://api.github.com/orgs/")[1].split("/")[0]
217
+ user_message = f'Organization name: "{org_name}" is unknown, "repository" config option should be updated. Please validate your repository config.'
218
+ elif "401 Client Error: Unauthorized for url" in message:
219
+ # 401 Client Error: Unauthorized for url: https://api.github.com/orgs/datarootsio/repos?per_page=100&sort=updated&direction=desc
220
+ user_message = (
221
+ "Github credentials have expired or changed, please review your credentials and re-authenticate or renew your access token."
222
+ )
223
+ return user_message
224
+
225
+ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> Tuple[bool, Any]:
226
+ config = self._validate_and_transform_config(config)
227
+ try:
228
+ authenticator = self._get_authenticator(config)
229
+ _, repositories = self._get_org_repositories(config=config, authenticator=authenticator)
230
+ if not repositories:
231
+ return (
232
+ False,
233
+ "Some of the provided repositories couldn't be found. Please verify if every entered repository has a valid name and it matches the following format: airbytehq/airbyte airbytehq/another-repo airbytehq/* airbytehq/airbyte.",
234
+ )
235
+ return True, None
236
+
237
+ except Exception as e:
238
+ message = repr(e)
239
+ user_message = self.user_friendly_error_message(message)
240
+ return False, user_message or message
241
+
242
+ def streams(self, config: Mapping[str, Any]) -> List[Stream]:
243
+ authenticator = self._get_authenticator(config)
244
+ config = self._validate_and_transform_config(config)
245
+ try:
246
+ organizations, repositories = self._get_org_repositories(config=config, authenticator=authenticator)
247
+ except Exception as e:
248
+ message = repr(e)
249
+ user_message = self.user_friendly_error_message(message)
250
+ if user_message:
251
+ raise AirbyteTracedException(
252
+ internal_message=message, message=user_message, failure_type=FailureType.config_error, exception=e
253
+ )
254
+ else:
255
+ raise e
256
+
257
+ if not any((organizations, repositories)):
258
+ user_message = (
259
+ "No streams available. Looks like your config for repositories or organizations is not valid."
260
+ " Please, check your permissions, names of repositories and organizations."
261
+ " Needed scopes: repo, read:org, read:repo_hook, read:user, read:discussion, workflow."
262
+ )
263
+ raise AirbyteTracedException(
264
+ internal_message="No streams available. Please check permissions",
265
+ message=user_message,
266
+ failure_type=FailureType.config_error,
267
+ )
268
+
269
+ # This parameter is deprecated and in future will be used sane default, page_size: 10
270
+ page_size = config.get("page_size_for_large_streams", constants.DEFAULT_PAGE_SIZE_FOR_LARGE_STREAM)
271
+ access_token_type, _ = self.get_access_token(config)
272
+
273
+ organization_args = {
274
+ "authenticator": authenticator,
275
+ "organizations": organizations,
276
+ "api_url": config.get("api_url"),
277
+ "access_token_type": access_token_type,
278
+ }
279
+ start_date = config.get("start_date")
280
+ organization_args_with_start_date = {**organization_args, "start_date": start_date}
281
+
282
+ repository_args = {
283
+ "authenticator": authenticator,
284
+ "api_url": config.get("api_url"),
285
+ "repositories": repositories,
286
+ "page_size_for_large_streams": page_size,
287
+ "access_token_type": access_token_type,
288
+ }
289
+ repository_args_with_start_date = {**repository_args, "start_date": start_date}
290
+
291
+ default_branches, branches_to_pull = self._get_branches_data(config.get("branch", []), repository_args)
292
+ pull_requests_stream = PullRequests(**repository_args_with_start_date)
293
+ projects_stream = Projects(**repository_args_with_start_date)
294
+ project_columns_stream = ProjectColumns(projects_stream, **repository_args_with_start_date)
295
+ teams_stream = Teams(**organization_args)
296
+ team_members_stream = TeamMembers(parent=teams_stream, **repository_args)
297
+ workflow_runs_stream = WorkflowRuns(**repository_args_with_start_date)
298
+
299
+ return [
300
+ IssueTimelineEvents(**repository_args),
301
+ Assignees(**repository_args),
302
+ Branches(**repository_args),
303
+ Collaborators(**repository_args),
304
+ Comments(**repository_args_with_start_date),
305
+ CommitCommentReactions(**repository_args_with_start_date),
306
+ CommitComments(**repository_args_with_start_date),
307
+ Commits(**repository_args_with_start_date, branches_to_pull=branches_to_pull, default_branches=default_branches),
308
+ ContributorActivity(**repository_args),
309
+ Deployments(**repository_args_with_start_date),
310
+ Events(**repository_args_with_start_date),
311
+ IssueCommentReactions(**repository_args_with_start_date),
312
+ IssueEvents(**repository_args_with_start_date),
313
+ IssueLabels(**repository_args),
314
+ IssueMilestones(**repository_args_with_start_date),
315
+ IssueReactions(**repository_args_with_start_date),
316
+ Issues(**repository_args_with_start_date),
317
+ Organizations(**organization_args),
318
+ ProjectCards(project_columns_stream, **repository_args_with_start_date),
319
+ project_columns_stream,
320
+ projects_stream,
321
+ PullRequestCommentReactions(**repository_args_with_start_date),
322
+ PullRequestCommits(parent=pull_requests_stream, **repository_args),
323
+ PullRequestStats(**repository_args_with_start_date),
324
+ ProjectsV2(**repository_args_with_start_date),
325
+ pull_requests_stream,
326
+ Releases(**repository_args_with_start_date),
327
+ Repositories(**organization_args_with_start_date),
328
+ ReviewComments(**repository_args_with_start_date),
329
+ Reviews(**repository_args_with_start_date),
330
+ Stargazers(**repository_args_with_start_date),
331
+ Tags(**repository_args),
332
+ teams_stream,
333
+ team_members_stream,
334
+ Users(**organization_args),
335
+ Workflows(**repository_args_with_start_date),
336
+ workflow_runs_stream,
337
+ WorkflowJobs(parent=workflow_runs_stream, **repository_args_with_start_date),
338
+ TeamMemberships(parent=team_members_stream, **repository_args),
339
+ ]
@@ -0,0 +1,179 @@
1
+ {
2
+ "documentationUrl": "https://docs.airbyte.com/integrations/sources/github",
3
+ "connectionSpecification": {
4
+ "$schema": "http://json-schema.org/draft-07/schema#",
5
+ "title": "GitHub Source Spec",
6
+ "type": "object",
7
+ "required": ["credentials", "repositories"],
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "credentials": {
11
+ "title": "Authentication",
12
+ "description": "Choose how to authenticate to GitHub",
13
+ "type": "object",
14
+ "order": 0,
15
+ "group": "auth",
16
+ "oneOf": [
17
+ {
18
+ "type": "object",
19
+ "title": "OAuth",
20
+ "required": ["access_token"],
21
+ "properties": {
22
+ "option_title": {
23
+ "type": "string",
24
+ "const": "OAuth Credentials",
25
+ "order": 0
26
+ },
27
+ "access_token": {
28
+ "type": "string",
29
+ "title": "Access Token",
30
+ "description": "OAuth access token",
31
+ "airbyte_secret": true
32
+ },
33
+ "client_id": {
34
+ "type": "string",
35
+ "title": "Client Id",
36
+ "description": "OAuth Client Id",
37
+ "airbyte_secret": true
38
+ },
39
+ "client_secret": {
40
+ "type": "string",
41
+ "title": "Client ssecret",
42
+ "description": "OAuth Client secret",
43
+ "airbyte_secret": true
44
+ }
45
+ }
46
+ },
47
+ {
48
+ "type": "object",
49
+ "title": "Personal Access Token",
50
+ "required": ["personal_access_token"],
51
+ "properties": {
52
+ "option_title": {
53
+ "type": "string",
54
+ "const": "PAT Credentials",
55
+ "order": 0
56
+ },
57
+ "personal_access_token": {
58
+ "type": "string",
59
+ "title": "Personal Access Tokens",
60
+ "description": "Log into GitHub and then generate a <a href=\"https://github.com/settings/tokens\">personal access token</a>. To load balance your API quota consumption across multiple API tokens, input multiple tokens separated with \",\"",
61
+ "airbyte_secret": true
62
+ }
63
+ }
64
+ }
65
+ ]
66
+ },
67
+ "repository": {
68
+ "type": "string",
69
+ "examples": [
70
+ "airbytehq/airbyte airbytehq/another-repo",
71
+ "airbytehq/*",
72
+ "airbytehq/airbyte"
73
+ ],
74
+ "title": "GitHub Repositories",
75
+ "description": "(DEPRCATED) Space-delimited list of GitHub organizations/repositories, e.g. `airbytehq/airbyte` for single repository, `airbytehq/*` for get all repositories from organization and `airbytehq/airbyte airbytehq/another-repo` for multiple repositories.",
76
+ "airbyte_hidden": true,
77
+ "pattern": "^([\\w.-]+/(\\*|[\\w.-]+(?<!\\.git))\\s+)*[\\w.-]+/(\\*|[\\w.-]+(?<!\\.git))$",
78
+ "pattern_descriptor": "org/repo org/another-repo org/*"
79
+ },
80
+ "repositories": {
81
+ "type": "array",
82
+ "items": {
83
+ "type": "string",
84
+ "pattern": "^([\\w.-]+/(\\*|[\\w.-]+(?<!\\.git))\\s+)*[\\w.-]+/(\\*|[\\w.-]+(?<!\\.git))$"
85
+ },
86
+ "minItems": 1,
87
+ "examples": [
88
+ "airbytehq/airbyte airbytehq/another-repo",
89
+ "airbytehq/*",
90
+ "airbytehq/airbyte"
91
+ ],
92
+ "title": "GitHub Repositories",
93
+ "description": "List of GitHub organizations/repositories, e.g. `airbytehq/airbyte` for single repository, `airbytehq/*` for get all repositories from organization and `airbytehq/airbyte airbytehq/another-repo` for multiple repositories.",
94
+ "order": 1,
95
+ "pattern_descriptor": "org/repo org/another-repo org/*"
96
+ },
97
+ "start_date": {
98
+ "type": "string",
99
+ "title": "Start date",
100
+ "description": "The date from which you'd like to replicate data from GitHub in the format YYYY-MM-DDT00:00:00Z. If the date is not set, all data will be replicated. For the streams which support this configuration, only data generated on or after the start date will be replicated. This field doesn't apply to all streams, see the <a href=\"https://docs.airbyte.com/integrations/sources/github\">docs</a> for more info",
101
+ "examples": ["2021-03-01T00:00:00Z"],
102
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$",
103
+ "pattern_descriptor": "YYYY-MM-DDTHH:mm:ssZ",
104
+ "order": 2,
105
+ "format": "date-time"
106
+ },
107
+ "api_url": {
108
+ "type": "string",
109
+ "examples": ["https://github.com", "https://github.company.org"],
110
+ "title": "API URL",
111
+ "default": "https://api.github.com/",
112
+ "description": "Please enter your basic URL from self-hosted GitHub instance or leave it empty to use GitHub.",
113
+ "order": 3
114
+ },
115
+ "branch": {
116
+ "type": "string",
117
+ "title": "Branch",
118
+ "examples": ["airbytehq/airbyte/master airbytehq/airbyte/my-branch"],
119
+ "description": "(DEPRCATED) Space-delimited list of GitHub repository branches to pull commits for, e.g. `airbytehq/airbyte/master`. If no branches are specified for a repository, the default branch will be pulled.",
120
+ "airbyte_hidden": true,
121
+ "pattern_descriptor": "org/repo/branch1 org/repo/branch2"
122
+ },
123
+ "branches": {
124
+ "type": "array",
125
+ "items": {
126
+ "type": "string"
127
+ },
128
+ "title": "Branches",
129
+ "examples": ["airbytehq/airbyte/master airbytehq/airbyte/my-branch"],
130
+ "description": "List of GitHub repository branches to pull commits for, e.g. `airbytehq/airbyte/master`. If no branches are specified for a repository, the default branch will be pulled.",
131
+ "order": 4,
132
+ "pattern_descriptor": "org/repo/branch1 org/repo/branch2"
133
+ }
134
+ }
135
+ },
136
+ "advanced_auth": {
137
+ "auth_flow_type": "oauth2.0",
138
+ "predicate_key": ["credentials", "option_title"],
139
+ "predicate_value": "OAuth Credentials",
140
+ "oauth_config_specification": {
141
+ "complete_oauth_output_specification": {
142
+ "type": "object",
143
+ "additionalProperties": false,
144
+ "properties": {
145
+ "access_token": {
146
+ "type": "string",
147
+ "path_in_connector_config": ["credentials", "access_token"]
148
+ }
149
+ }
150
+ },
151
+ "complete_oauth_server_input_specification": {
152
+ "type": "object",
153
+ "additionalProperties": false,
154
+ "properties": {
155
+ "client_id": {
156
+ "type": "string"
157
+ },
158
+ "client_secret": {
159
+ "type": "string"
160
+ }
161
+ }
162
+ },
163
+ "complete_oauth_server_output_specification": {
164
+ "type": "object",
165
+ "additionalProperties": false,
166
+ "properties": {
167
+ "client_id": {
168
+ "type": "string",
169
+ "path_in_connector_config": ["credentials", "client_id"]
170
+ },
171
+ "client_secret": {
172
+ "type": "string",
173
+ "path_in_connector_config": ["credentials", "client_secret"]
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }