Skip to content

Commit d03c97a

Browse files
committed
CRAVEX SourceHut workflow integration #348
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 768c609 commit d03c97a

13 files changed

Lines changed: 251 additions & 12 deletions

File tree

dje/admin.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,10 +1057,11 @@ class DataspaceConfigurationForm(forms.ModelForm):
10571057
"scancodeio_api_key",
10581058
"vulnerablecode_api_key",
10591059
"purldb_api_key",
1060+
"forgejo_token",
10601061
"github_token",
10611062
"gitlab_token",
10621063
"jira_token",
1063-
"forgejo_token",
1064+
"sourcehut_token",
10641065
]
10651066

10661067
def __init__(self, *args, **kwargs):
@@ -1101,6 +1102,14 @@ class DataspaceConfigurationInline(DataspacedFKMixin, admin.StackedInline):
11011102
)
11021103
},
11031104
),
1105+
(
1106+
"Forgejo Integration",
1107+
{
1108+
"fields": [
1109+
"forgejo_token",
1110+
]
1111+
},
1112+
),
11041113
(
11051114
"GitHub Integration",
11061115
{
@@ -1127,10 +1136,10 @@ class DataspaceConfigurationInline(DataspacedFKMixin, admin.StackedInline):
11271136
},
11281137
),
11291138
(
1130-
"Forgejo Integration",
1139+
"SourceHut Integration",
11311140
{
11321141
"fields": [
1133-
"forgejo_token",
1142+
"sourcehut_token",
11341143
]
11351144
},
11361145
),

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Welcome to the very start of your DejaCode journey!
5252
integrations-github
5353
integrations-gitlab
5454
integrations-jira
55+
integrations-sourcehut
5556

5657
.. toctree::
5758
:maxdepth: 1

docs/integrations-sourcehut.rst

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
.. _integrations_sourcehut:
2+
3+
SourceHut Integration
4+
=====================
5+
6+
DejaCode's integration with SourceHut allows you to automatically forward
7+
**Workflow Requests** to SourceHut **tickets**.
8+
This behavior can be selectively applied to any **Request Template** of your choice.
9+
10+
Prerequisites
11+
-------------
12+
13+
- A **SourceHut project** hosted on https://todo.sr.ht that you want to integrate with
14+
DejaCode.
15+
- A **SourceHut account** with API access and permission to create/edit tickets.
16+
17+
SourceHut API Token
18+
-------------------
19+
20+
To enable integration, you need a SourceHut **API token**.
21+
22+
1. **Create a Token**:
23+
24+
- Go to https://meta.sr.ht/oauth2
25+
- Under **Personal Access Tokens**, click **"Generate new token"**
26+
- Set a clear description like ``DejaCode Integration`` in the "Comment" field
27+
- Select only the ``todo.sr.ht`` > ``TICKETS`` scope
28+
- **Generate token** and copy the token
29+
30+
.. note::
31+
32+
It is recommended to **create a dedicated SourceHut user** with a clear, descriptive
33+
name such as ``dejacode-integration``. This ensures that all SourceHut issues
34+
managed by integration are clearly attributed to that user, improving traceability
35+
and auditability.
36+
37+
DejaCode Dataspace Configuration
38+
--------------------------------
39+
40+
To use your SourceHut token in DejaCode:
41+
42+
1. Go to the **Administration dashboard**
43+
2. Navigate to **Dataspaces**, and select your Dataspace
44+
3. Scroll to the **SourceHut Integration** section under **Configuration**
45+
4. Paste your SourceHut token in the **SourceHut token** field
46+
5. Save the form
47+
48+
Activate SourceHut Integration on Request Templates
49+
---------------------------------------------------
50+
51+
1. Go to the **Administration dashboard**
52+
2. Navigate to **Workflow** > **Request templates**
53+
3. Create or edit a Request Template in your Dataspace
54+
4. Set the **Issue Tracker ID** field to your SourceHut project URL, e.g.::
55+
56+
https://todo.sr.ht/~USERNAME/PROJECT_NAME
57+
58+
Once the integration is configured:
59+
60+
- New **Requests** using this template will be automatically pushed to SourceHut
61+
- Field updates (like title or priority) and **status changes** (e.g. closed) will be
62+
synced
63+
- New **Comments** on a DejaCode Request will be propagated to the SourceHut ticket

workflow/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def clean_issue_tracker_id(self):
6969
"• GitHub: https://github.com/ORG/REPO_NAME",
7070
"• GitLab: https://gitlab.com/GROUP/PROJECT_NAME",
7171
"• Jira: https://YOUR_DOMAIN.atlassian.net/projects/PROJECTKEY",
72+
"• SourceHut: https://todo.sr.ht/~USERNAME/PROJECT_NAME",
7273
]
7374
)
7475
return issue_tracker_id

workflow/integrations/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
from workflow.integrations.github import GitHubIntegration
1414
from workflow.integrations.gitlab import GitLabIntegration
1515
from workflow.integrations.jira import JiraIntegration
16+
from workflow.integrations.sourcehut import SourceHutIntegration
1617

1718
__all__ = [
1819
"BaseIntegration",
1920
"ForgejoIntegration",
2021
"GitHubIntegration",
2122
"GitLabIntegration",
2223
"JiraIntegration",
24+
"SourceHutIntegration",
2325
"is_valid_issue_tracker_id",
2426
"get_class_for_tracker",
2527
"get_class_for_platform",
@@ -36,11 +38,14 @@
3638
r"/(?:projects|browse)/[A-Z][A-Z0-9]+(?:/[^/]*)*/*$"
3739
)
3840

41+
SOURCEHUT_PATTERN = re.compile(r"^https://todo\.sr\.ht/~[^/]+/[^/]+/?$")
42+
3943
ISSUE_TRACKER_PATTERNS = [
4044
FORGEJO_PATTERN,
4145
GITHUB_PATTERN,
4246
GITLAB_PATTERN,
4347
JIRA_PATTERN,
48+
SOURCEHUT_PATTERN,
4449
]
4550

4651

@@ -57,6 +62,8 @@ def get_class_for_tracker(issue_tracker_id):
5762
return JiraIntegration
5863
elif "forgejo" in issue_tracker_id:
5964
return ForgejoIntegration
65+
elif "todo.sr.ht" in issue_tracker_id:
66+
return SourceHutIntegration
6067

6168

6269
def get_class_for_platform(platform):
@@ -65,4 +72,5 @@ def get_class_for_platform(platform):
6572
"github": GitHubIntegration,
6673
"gitlab": GitLabIntegration,
6774
"jira": JiraIntegration,
75+
"sourcehut": SourceHutIntegration,
6876
}.get(platform)

workflow/integrations/base.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class BaseIntegration:
2121
"""Base class for managing issue tracker integrations from DejaCode requests."""
2222

2323
default_timeout = 10
24+
open_status = "open"
25+
closed_status = "closed"
2426

2527
def __init__(self, dataspace, timeout=None):
2628
if not dataspace:
@@ -95,6 +97,11 @@ def patch(self, url, json=None):
9597
def post_comment(self, repo_id, issue_id, comment_body, base_url=None):
9698
raise NotImplementedError
9799

100+
def get_status(self, request):
101+
if request.is_closed:
102+
return self.closed_status
103+
return self.open_status
104+
98105
@staticmethod
99106
def make_issue_title(request):
100107
return f"[DEJACODE] {request.title}"

workflow/integrations/forgejo.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ class ForgejoIntegration(BaseIntegration):
1919
from DejaCode requests.
2020
"""
2121

22-
open_status = "open"
23-
closed_status = "closed"
24-
2522
def get_headers(self):
2623
forgejo_token = self.dataspace.get_configuration("forgejo_token")
2724
if not forgejo_token:
@@ -46,7 +43,7 @@ def sync(self, request):
4643
issue_id=external_issue.issue_id,
4744
title=self.make_issue_title(request),
4845
body=self.make_issue_body(request),
49-
state=self.closed_status if request.is_closed else self.open_status,
46+
state=self.get_status(request),
5047
)
5148
else:
5249
issue = self.create_issue(

workflow/integrations/github.py

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

2222
api_url = GITHUB_API_URL
23-
open_status = "open"
24-
closed_status = "closed"
2523

2624
def get_headers(self):
2725
github_token = self.dataspace.get_configuration(field_name="github_token")
@@ -47,7 +45,7 @@ def sync(self, request):
4745
issue_id=external_issue.issue_id,
4846
title=self.make_issue_title(request),
4947
body=self.make_issue_body(request),
50-
state=self.closed_status if request.is_closed else self.open_status,
48+
state=self.get_status(request),
5149
labels=labels,
5250
)
5351
else:

workflow/integrations/gitlab.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class GitLabIntegration(BaseIntegration):
2121
"""
2222

2323
api_url = GITLAB_API_URL
24+
open_status = "reopen"
25+
closed_status = "close"
2426

2527
def get_headers(self):
2628
gitlab_token = self.dataspace.get_configuration(field_name="gitlab_token")
@@ -48,7 +50,7 @@ def sync(self, request):
4850
issue_id=external_issue.issue_id,
4951
title=self.make_issue_title(request),
5052
body=self.make_issue_body(request),
51-
state_event="close" if request.is_closed else "reopen",
53+
state_event=self.get_status(request),
5254
labels=labels,
5355
)
5456
else:

workflow/integrations/jira.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class JiraIntegration(BaseIntegration):
2222
"""
2323

2424
issuetype = "DejaCode Request"
25+
open_status = None
2526
closed_status = "Closed"
2627

2728
def get_headers(self):
@@ -55,7 +56,7 @@ def sync(self, request):
5556
issue_id=external_issue.issue_id,
5657
title=self.make_issue_title(request),
5758
body=self.make_issue_body(request),
58-
status=self.closed_status if request.is_closed else None,
59+
status=self.get_status(request),
5960
)
6061
else:
6162
issue = self.create_issue(

0 commit comments

Comments
 (0)