Skip to content

Commit cf6f480

Browse files
committed
Add base_url field on the ExternalIssueLink model #350
Signed-off-by: tdruez <[email protected]>
1 parent 9e0cdf5 commit cf6f480

File tree

9 files changed

+78
-26
lines changed

9 files changed

+78
-26
lines changed

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ Welcome to the very start of your DejaCode journey!
4848
:maxdepth: 1
4949
:caption: Integrations
5050

51+
integrations-forgejo
5152
integrations-github
5253
integrations-gitlab
5354
integrations-jira
54-
integrations-forgejo
5555

5656
.. toctree::
5757
:maxdepth: 1

workflow/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def clean_issue_tracker_id(self):
6565
raise ValidationError(
6666
[
6767
"Invalid issue tracker URL format. Supported formats include:",
68+
"• Forgejo: https://forgejo.DOMAIN.org/OR/REPO_NAME",
6869
"• GitHub: https://github.com/ORG/REPO_NAME",
6970
"• GitLab: https://gitlab.com/GROUP/PROJECT_NAME",
7071
"• Jira: https://YOUR_DOMAIN.atlassian.net/projects/PROJECTKEY",

workflow/integrations/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ def patch(self, url, json=None):
9292
"""Send a PATCH request."""
9393
return self.request("PATCH", url, json=json)
9494

95+
def post_comment(self, repo_id, issue_id, comment_body, base_url=None):
96+
raise NotImplementedError
97+
9598
@staticmethod
9699
def make_issue_title(request):
97100
return f"[DEJACODE] {request.title}"

workflow/integrations/forgejo.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from workflow.integrations.base import BaseIntegration
1212

13-
FORGEJO_API_URL = ""
13+
FORGEJO_API_PATH = "/api/v1"
1414

1515

1616
class ForgejoIntegration(BaseIntegration):
@@ -19,40 +19,46 @@ class ForgejoIntegration(BaseIntegration):
1919
from DejaCode requests.
2020
"""
2121

22-
api_url = FORGEJO_API_URL
22+
open_status = "open"
23+
closed_status = "closed"
2324

2425
def get_headers(self):
25-
forgejo_token = self.dataspace.get_configuration(field_name="forgejo_token")
26+
forgejo_token = self.dataspace.get_configuration("forgejo_token")
2627
if not forgejo_token:
2728
raise ValueError("The forgejo_token is not set on the Dataspace.")
2829
return {"Authorization": f"token {forgejo_token}"}
2930

3031
def sync(self, request):
3132
"""Sync the given request with Forgejo by creating or updating an issue."""
3233
try:
33-
repo_id = self.extract_forgejo_repo_path(request.request_template.issue_tracker_id)
34+
base_url, repo_path = self.extract_forgejo_info(
35+
request.request_template.issue_tracker_id
36+
)
3437
except ValueError as error:
35-
raise ValueError(f"Invalid Forgejo repository URL: {error}")
38+
raise ValueError(f"Invalid Forgejo tracker URL: {error}")
39+
40+
self.api_url = base_url.rstrip("/") + FORGEJO_API_PATH
3641

3742
external_issue = request.external_issue
3843
if external_issue:
3944
self.update_issue(
40-
repo_id=repo_id,
45+
repo_id=repo_path,
4146
issue_id=external_issue.issue_id,
4247
title=self.make_issue_title(request),
4348
body=self.make_issue_body(request),
44-
state="closed" if request.is_closed else "open",
49+
state=self.closed_status if request.is_closed else self.open_status,
4550
)
4651
else:
4752
issue = self.create_issue(
48-
repo_id=repo_id,
53+
repo_id=repo_path,
4954
title=self.make_issue_title(request),
5055
body=self.make_issue_body(request),
5156
)
5257
request.link_external_issue(
5358
platform="forgejo",
54-
repo=repo_id,
59+
repo=repo_path,
5560
issue_id=issue["number"],
61+
base_url=base_url,
5662
)
5763

5864
def create_issue(self, repo_id, title, body=""):
@@ -78,22 +84,28 @@ def update_issue(self, repo_id, issue_id, title=None, body=None, state=None):
7884

7985
return self.patch(url, json=data)
8086

81-
def post_comment(self, repo_id, issue_id, comment_body):
87+
def post_comment(self, repo_id, issue_id, comment_body, base_url=None):
8288
"""Post a comment on an existing Forgejo issue."""
83-
url = f"{self.api_url}/repos/{repo_id}/issues/{issue_id}/comments"
84-
data = {"body": comment_body}
85-
86-
return self.post(url, json=data)
89+
url = f"{base_url}/{FORGEJO_API_PATH}/repos/{repo_id}/issues/{issue_id}/comments"
90+
return self.post(url, json={"body": comment_body})
8791

8892
@staticmethod
89-
def extract_forgejo_repo_path(url):
90-
"""Extract 'owner/repo-name' from a Forgejo URL."""
93+
def extract_forgejo_info(url):
94+
"""
95+
Extract the Forgejo base domain and repo path (org/repo) from a repo URL.
96+
97+
Example:
98+
- https://forgejo.example.org/org/repo → ("https://forgejo.example.org", "org/repo")
99+
100+
"""
91101
parsed = urlparse(url)
92102
if not parsed.netloc:
93-
raise ValueError("URL must include a hostname.")
103+
raise ValueError("Missing hostname in Forgejo URL.")
94104

95-
path_parts = [part for part in parsed.path.split("/") if part]
105+
base_url = f"{parsed.scheme}://{parsed.netloc}"
106+
path_parts = [p for p in parsed.path.split("/") if p]
96107
if len(path_parts) < 2:
97108
raise ValueError("Incomplete Forgejo repository path.")
98109

99-
return f"{path_parts[0]}/{path_parts[1]}"
110+
repo_path = f"{path_parts[0]}/{path_parts[1]}"
111+
return base_url, repo_path

workflow/integrations/github.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class GitHubIntegration(BaseIntegration):
2020
"""
2121

2222
api_url = GITHUB_API_URL
23+
open_status = "open"
24+
closed_status = "closed"
2325

2426
def get_headers(self):
2527
github_token = self.dataspace.get_configuration(field_name="github_token")
@@ -45,7 +47,7 @@ def sync(self, request):
4547
issue_id=external_issue.issue_id,
4648
title=self.make_issue_title(request),
4749
body=self.make_issue_body(request),
48-
state="closed" if request.is_closed else "open",
50+
state=self.closed_status if request.is_closed else self.open_status,
4951
labels=labels,
5052
)
5153
else:
@@ -88,7 +90,7 @@ def update_issue(self, repo_id, issue_id, title=None, body=None, state=None, lab
8890

8991
return self.patch(url, json=data)
9092

91-
def post_comment(self, repo_id, issue_id, comment_body):
93+
def post_comment(self, repo_id, issue_id, comment_body, base_url=None):
9294
"""Post a comment on an existing GitHub issue."""
9395
url = f"{self.api_url}/repos/{repo_id}/issues/{issue_id}/comments"
9496
data = {"body": comment_body}

workflow/integrations/gitlab.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def update_issue(self, repo_id, issue_id, title=None, body=None, state_event=Non
9595

9696
return self.put(url, json=data)
9797

98-
def post_comment(self, repo_id, issue_id, comment_body):
98+
def post_comment(self, repo_id, issue_id, comment_body, base_url=None):
9999
"""Post a comment on an existing GitLab issue."""
100100
project_path = quote(repo_id, safe="")
101101
url = f"{self.api_url}/projects/{project_path}/issues/{issue_id}/notes"

workflow/integrations/jira.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def update_issue(self, issue_id, title=None, body=None, status=None):
100100

101101
return {"id": issue_id}
102102

103-
def post_comment(self, repo_id, issue_id, comment_body):
103+
def post_comment(self, repo_id, issue_id, comment_body, base_url=None):
104104
"""Post a comment on an existing Jira issue."""
105105
api_url = repo_id.rstrip("/") + JIRA_API_PATH
106106
url = f"{api_url}/issue/{issue_id}/comment"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2.4 on 2025-08-07 14:07
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('workflow', '0002_requesttemplate_issue_tracker_id_externalissuelink_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='externalissuelink',
15+
name='base_url',
16+
field=models.URLField(blank=True, help_text='Base URL of the external issue tracker platform (e.g., https://forgejo.example.org). Used to construct API endpoints for integrations like Forgejo or Jira.', max_length=255, null=True),
17+
),
18+
]

workflow/models.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ class Platform(models.TextChoices):
113113
help_text=_("ID or key of the issue on the external platform."),
114114
)
115115

116+
base_url = models.URLField(
117+
max_length=255,
118+
blank=True,
119+
null=True,
120+
help_text=_(
121+
"Base URL of the external issue tracker platform (e.g., https://forgejo.example.org). "
122+
"Used to construct API endpoints for integrations like Forgejo or Jira."
123+
),
124+
)
125+
116126
class Meta:
117127
unique_together = (
118128
("dataspace", "platform", "repo", "issue_id"),
@@ -131,7 +141,7 @@ def issue_url(self):
131141
elif self.platform == self.Platform.JIRA:
132142
return f"{self.repo}/browse/{self.issue_id}"
133143
elif self.platform == self.Platform.FORGEJO:
134-
return f"{self.repo}/issues/{self.issue_id}"
144+
return f"{self.base_url}/{self.repo}/issues/{self.issue_id}"
135145

136146
@property
137147
def icon_css_class(self):
@@ -606,16 +616,20 @@ def close(self, user, reason):
606616
)
607617
return event_instance
608618

609-
def link_external_issue(self, platform, repo, issue_id):
619+
def link_external_issue(self, platform, repo, issue_id, base_url=None):
610620
"""Create or return an ExternalIssueLink associated with this Request."""
611621
if self.external_issue:
612622
return self.external_issue
613623

624+
if base_url:
625+
base_url = base_url.rstrip("/")
626+
614627
external_issue = ExternalIssueLink.objects.create(
615628
dataspace=self.dataspace,
616629
platform=platform,
617630
repo=repo,
618631
issue_id=str(issue_id),
632+
base_url=base_url,
619633
)
620634

621635
# Set the external_issue on this instance without triggering the whole
@@ -710,6 +724,7 @@ def handle_integrations(self):
710724
repo_id=external_issue.repo,
711725
issue_id=external_issue.issue_id,
712726
comment_body=self.text,
727+
base_url=external_issue.base_url,
713728
)
714729

715730

@@ -792,6 +807,7 @@ def handle_integrations(self):
792807
repo_id=external_issue.repo,
793808
issue_id=external_issue.issue_id,
794809
comment_body=self.text,
810+
base_url=external_issue.base_url,
795811
)
796812

797813

0 commit comments

Comments
 (0)