-
-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathgithub.py
More file actions
176 lines (146 loc) · 5.61 KB
/
Copy pathgithub.py
File metadata and controls
176 lines (146 loc) · 5.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# DejaCode is a trademark of nexB Inc.
# SPDX-License-Identifier: AGPL-3.0-only
# See https://github.com/aboutcode-org/dejacode for support or download.
# See https://aboutcode.org for more information about AboutCode FOSS projects.
#
from urllib.parse import urlparse
from django.conf import settings
import requests
GITHUB_API_URL = "https://api.github.com"
DEJACODE_SITE_URL = settings.SITE_URL.rstrip("/")
class GitHubIntegration:
"""
A class for managing GitHub issue creation, updates, and comments
from DejaCode requests.
"""
api_url = GITHUB_API_URL
default_timeout = 10
def __init__(self, dataspace):
if not dataspace:
raise ValueError("Dataspace must be provided.")
self.dataspace = dataspace
self.session = self.get_session()
def get_session(self):
session = requests.Session()
session.headers.update(self.get_headers())
return session
def get_headers(self):
github_token = self.dataspace.get_configuration(field_name="github_token")
if not github_token:
raise ValueError("The github_token is not set on the Dataspace.")
return {"Authorization": f"token {github_token}"}
def sync(self, request):
"""Sync the given request with GitHub by creating or updating an issue."""
try:
repo_id = self.extract_github_repo_path(request.request_template.issue_tracker_id)
except ValueError as error:
raise ValueError(f"Invalid GitHub repository URL: {error}")
labels = []
if request.priority:
labels.append(str(request.priority))
external_issue = request.external_issue
if external_issue:
self.update_issue(
repo_id=repo_id,
issue_id=external_issue.issue_id,
title=self.make_issue_title(request),
body=self.make_issue_body(request),
state="closed" if request.is_closed else "open",
)
else:
issue = self.create_issue(
repo_id=repo_id,
title=self.make_issue_title(request),
body=self.make_issue_body(request),
labels=labels,
)
request.link_external_issue(
platform="github",
repo=repo_id,
issue_id=issue["number"],
)
def create_issue(self, repo_id, title, body="", labels=None):
"""Create a new GitHub issue."""
url = f"{self.api_url}/repos/{repo_id}/issues"
data = {
"title": title,
"body": body,
}
if labels:
data["labels"] = labels
response = self.session.post(
url,
json=data,
timeout=self.default_timeout,
)
response.raise_for_status()
return response.json()
def update_issue(self, repo_id, issue_id, title=None, body=None, state=None):
"""Update an existing GitHub issue."""
url = f"{self.api_url}/repos/{repo_id}/issues/{issue_id}"
data = {}
if title:
data["title"] = title
if body:
data["body"] = body
if state:
data["state"] = state
response = self.session.patch(
url,
json=data,
timeout=self.default_timeout,
)
response.raise_for_status()
return response.json()
def post_comment(self, repo_id, issue_id, comment_body):
"""Post a comment on an existing GitHub issue."""
url = f"{self.api_url}/repos/{repo_id}/issues/{issue_id}/comments"
data = {"body": comment_body}
response = self.session.post(
url,
json=data,
timeout=self.default_timeout,
)
response.raise_for_status()
return response.json()
@staticmethod
def extract_github_repo_path(url):
"""Extract 'username/repo-name' from a GitHub URL."""
parsed = urlparse(url)
if "github.com" not in parsed.netloc:
raise ValueError("URL does not point to GitHub.")
path_parts = [part for part in parsed.path.split("/") if part]
if len(path_parts) < 2:
raise ValueError("Incomplete GitHub repository path.")
return f"{path_parts[0]}/{path_parts[1]}"
@staticmethod
def make_issue_title(request):
return f"[DEJACODE] {request.title}"
@staticmethod
def make_issue_body(request):
request_url = f"{DEJACODE_SITE_URL}{request.get_absolute_url()}"
label_fields = [
("📝 Request Template", request.request_template),
("📦 Product Context", request.product_context),
("📌 Applies To", request.content_object),
("🙋 Submitted By", request.requester),
("👤 Assigned To", request.assignee),
("🚨 Priority", request.priority),
("🗒️ Notes", request.notes),
("🔗️ DejaCode URL", request_url),
]
lines = []
for label, value in label_fields:
if value:
lines.append(f"### {label}\n{value}")
lines.append("----")
for question in request.get_serialized_data_as_list():
label = question.get("label")
value = question.get("value")
input_type = question.get("input_type")
if input_type == "BooleanField":
value = "Yes" if str(value).lower() in ("1", "true", "yes") else "No"
lines.append(f"### {label}\n{value}")
return "\n\n".join(lines)