|
39 | 39 | from dje.models import HistoryDateFieldsMixin |
40 | 40 | from dje.models import HistoryFieldsMixin |
41 | 41 | from dje.models import get_unsecured_manager |
| 42 | +from workflow.integrations import github |
42 | 43 | from workflow.notification import request_comment_slack_payload |
43 | 44 | from workflow.notification import request_slack_payload |
44 | 45 |
|
@@ -88,13 +89,51 @@ def __str__(self): |
88 | 89 | return self.label |
89 | 90 |
|
90 | 91 |
|
| 92 | +class ExternalIssueLink(DataspacedModel): |
| 93 | + class Platform(models.TextChoices): |
| 94 | + GITHUB = "github", _("GitHub") |
| 95 | + GITLAB = "gitlab", _("GitLab") |
| 96 | + JIRA = "jira", _("Jira") |
| 97 | + |
| 98 | + platform = models.CharField( |
| 99 | + max_length=20, choices=Platform.choices, help_text="External issue tracking platform." |
| 100 | + ) |
| 101 | + |
| 102 | + repo = models.CharField( |
| 103 | + max_length=100, help_text="Repository or project identifier (e.g., 'user/repo-name')." |
| 104 | + ) |
| 105 | + |
| 106 | + issue_id = models.CharField( |
| 107 | + max_length=100, help_text="ID or key of the issue on the external platform." |
| 108 | + ) |
| 109 | + |
| 110 | + class Meta: |
| 111 | + unique_together = ( |
| 112 | + ("dataspace", "platform", "repo", "issue_id"), |
| 113 | + ("dataspace", "uuid"), |
| 114 | + ) |
| 115 | + |
| 116 | + def __str__(self): |
| 117 | + return f"{self.get_platform_display()}:{self.repo}#{self.issue_id}" |
| 118 | + |
| 119 | + @property |
| 120 | + def issue_url(self): |
| 121 | + if self.platform == self.Platform.GITHUB: |
| 122 | + return f"https://github.com/{self.repo}/issues/{self.issue_id}" |
| 123 | + elif self.platform == self.Platform.GITLAB: |
| 124 | + return f"https://gitlab.com/{self.repo}/-/issues/{self.issue_id}" |
| 125 | + elif self.platform == self.Platform.JIRA: |
| 126 | + return f"https://{self.repo}/browse/{self.issue_id}" |
| 127 | + |
| 128 | + |
91 | 129 | class RequestQuerySet(DataspacedQuerySet): |
92 | 130 | BASE_SELECT_RELATED = [ |
93 | 131 | "request_template", |
94 | 132 | "requester", |
95 | 133 | "assignee", |
96 | 134 | "priority", |
97 | 135 | "product_context", |
| 136 | + "external_issue", |
98 | 137 | "last_modified_by", |
99 | 138 | ] |
100 | 139 |
|
@@ -348,6 +387,14 @@ class Status(models.TextChoices): |
348 | 387 | ), |
349 | 388 | ) |
350 | 389 |
|
| 390 | + external_issue = models.ForeignKey( |
| 391 | + to="workflow.ExternalIssueLink", |
| 392 | + on_delete=models.SET_NULL, |
| 393 | + null=True, |
| 394 | + blank=True, |
| 395 | + help_text=_("Link to external issue (GitHub, GitLab, Jira, etc.)"), |
| 396 | + ) |
| 397 | + |
351 | 398 | last_modified_by = LastModifiedByField() |
352 | 399 |
|
353 | 400 | objects = DataspacedManager.from_queryset(RequestQuerySet)() |
@@ -397,6 +444,8 @@ def save(self, *args, **kwargs): |
397 | 444 | return |
398 | 445 | previous_object.update_request_count() |
399 | 446 |
|
| 447 | + self.handle_integrations() |
| 448 | + |
400 | 449 | def get_absolute_url(self): |
401 | 450 | return reverse("workflow:request_details", args=[self.uuid]) |
402 | 451 |
|
@@ -520,6 +569,29 @@ def serialize_hook(self, hook): |
520 | 569 | "data": serializer.data, |
521 | 570 | } |
522 | 571 |
|
| 572 | + def link_external_issue(self, platform, repo, issue_id): |
| 573 | + """Create or return an ExternalIssueLink associated with this Request.""" |
| 574 | + if self.external_issue: |
| 575 | + return self.external_issue |
| 576 | + |
| 577 | + external_issue = ExternalIssueLink.objects.create( |
| 578 | + dataspace=self.dataspace, |
| 579 | + platform=platform, |
| 580 | + repo=repo, |
| 581 | + issue_id=str(issue_id), |
| 582 | + ) |
| 583 | + self.update(external_issue=external_issue) |
| 584 | + |
| 585 | + return external_issue |
| 586 | + |
| 587 | + def handle_integrations(self): |
| 588 | + issue_tracker_id = self.request_template.issue_tracker_id |
| 589 | + if not issue_tracker_id: |
| 590 | + return |
| 591 | + |
| 592 | + if "github.com" in issue_tracker_id: |
| 593 | + github.handle_integration(request=self) |
| 594 | + |
523 | 595 |
|
524 | 596 | @receiver(models.signals.post_delete, sender=Request) |
525 | 597 | def update_request_count_on_delete(sender, instance=None, **kwargs): |
@@ -723,6 +795,16 @@ class RequestTemplate(HistoryFieldsMixin, DataspacedModel): |
723 | 795 | ), |
724 | 796 | ) |
725 | 797 |
|
| 798 | + issue_tracker_id = models.CharField( |
| 799 | + verbose_name=_("Issue Tracker ID"), |
| 800 | + max_length=1000, |
| 801 | + blank=True, |
| 802 | + help_text=_( |
| 803 | + "Link to associated issue in a tracking application, " |
| 804 | + "provided by the integration when the issue is created." |
| 805 | + ), |
| 806 | + ) |
| 807 | + |
726 | 808 | objects = DataspacedManager.from_queryset(RequestTemplateQuerySet)() |
727 | 809 |
|
728 | 810 | class Meta: |
|
0 commit comments