feat: issue links (#288)

* feat: links for issues

* fix: add issue link in serilaizer

* feat: links can be added to issues

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
pablohashescobar 2023-02-17 17:04:12 +05:30 committed by GitHub
parent a66b2fd73d
commit 7c1f357bed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 331 additions and 65 deletions

View file

@ -24,9 +24,15 @@ from plane.db.models import (
Cycle,
Module,
ModuleIssue,
IssueLink,
)
class IssueLinkCreateSerializer(serializers.Serializer):
url = serializers.CharField(required=True)
title = serializers.CharField(required=False)
class IssueFlatSerializer(BaseSerializer):
## Contain only flat fields
@ -86,6 +92,11 @@ class IssueCreateSerializer(BaseSerializer):
write_only=True,
required=False,
)
links_list = serializers.ListField(
child=IssueLinkCreateSerializer(),
write_only=True,
required=False,
)
class Meta:
model = Issue
@ -104,6 +115,7 @@ class IssueCreateSerializer(BaseSerializer):
assignees = validated_data.pop("assignees_list", None)
labels = validated_data.pop("labels_list", None)
blocks = validated_data.pop("blocks_list", None)
links = validated_data.pop("links_list", None)
project = self.context["project"]
issue = Issue.objects.create(**validated_data, project=project)
@ -172,6 +184,24 @@ class IssueCreateSerializer(BaseSerializer):
batch_size=10,
)
if links is not None:
IssueLink.objects.bulk_create(
[
IssueLink(
issue=issue,
project=project,
workspace=project.workspace,
created_by=issue.created_by,
updated_by=issue.updated_by,
title=link.get("title", None),
url=link.get("url", None),
)
for link in links
],
batch_size=10,
ignore_conflicts=True,
)
return issue
def update(self, instance, validated_data):
@ -179,6 +209,7 @@ class IssueCreateSerializer(BaseSerializer):
assignees = validated_data.pop("assignees_list", None)
labels = validated_data.pop("labels_list", None)
blocks = validated_data.pop("blocks_list", None)
links = validated_data.pop("links_list", None)
if blockers is not None:
IssueBlocker.objects.filter(block=instance).delete()
@ -248,6 +279,25 @@ class IssueCreateSerializer(BaseSerializer):
batch_size=10,
)
if links is not None:
IssueLink.objects.filter(issue=instance).delete()
IssueLink.objects.bulk_create(
[
IssueLink(
issue=instance,
project=instance.project,
workspace=instance.project.workspace,
created_by=instance.created_by,
updated_by=instance.updated_by,
title=link.get("title", None),
url=link.get("url", None),
)
for link in links
],
batch_size=10,
ignore_conflicts=True,
)
return super().update(instance, validated_data)
@ -410,6 +460,12 @@ class IssueModuleDetailSerializer(BaseSerializer):
]
class IssueLinkSerializer(BaseSerializer):
class Meta:
model = IssueLink
fields = "__all__"
class IssueSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project")
state_detail = StateSerializer(read_only=True, source="state")
@ -422,6 +478,7 @@ class IssueSerializer(BaseSerializer):
blocker_issues = BlockerIssueSerializer(read_only=True, many=True)
issue_cycle = IssueCycleDetailSerializer(read_only=True)
issue_module = IssueModuleDetailSerializer(read_only=True)
issue_link = IssueLinkSerializer(read_only=True, many=True)
sub_issues_count = serializers.IntegerField(read_only=True)
class Meta:

View file

@ -39,6 +39,7 @@ from plane.db.models import (
IssueBlocker,
CycleIssue,
ModuleIssue,
IssueLink,
)
from plane.bgtasks.issue_activites_task import issue_activity
@ -75,7 +76,6 @@ class IssueViewSet(BaseViewSet):
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
)
if current_instance is not None:
issue_activity.delay(
{
"type": "issue.activity",
@ -92,7 +92,6 @@ class IssueViewSet(BaseViewSet):
return super().perform_update(serializer)
def get_queryset(self):
return (
super()
.get_queryset()
@ -136,6 +135,12 @@ class IssueViewSet(BaseViewSet):
).prefetch_related("module__members"),
),
)
.prefetch_related(
Prefetch(
"issue_link",
queryset=IssueLink.objects.select_related("issue"),
)
)
)
def grouper(self, issue, group_by):
@ -265,6 +270,12 @@ class UserWorkSpaceIssues(BaseAPIView):
queryset=ModuleIssue.objects.select_related("module", "issue"),
),
)
.prefetch_related(
Prefetch(
"issue_link",
queryset=IssueLink.objects.select_related("issue"),
)
)
)
serializer = IssueSerializer(issues, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@ -277,7 +288,6 @@ class UserWorkSpaceIssues(BaseAPIView):
class WorkSpaceIssuesEndpoint(BaseAPIView):
permission_classes = [
WorkSpaceAdminPermission,
]
@ -298,7 +308,6 @@ class WorkSpaceIssuesEndpoint(BaseAPIView):
class IssueActivityEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
@ -333,7 +342,6 @@ class IssueActivityEndpoint(BaseAPIView):
class IssueCommentViewSet(BaseViewSet):
serializer_class = IssueCommentSerializer
model = IssueComment
permission_classes = [
@ -436,7 +444,6 @@ class IssuePropertyViewSet(BaseViewSet):
def create(self, request, slug, project_id):
try:
issue_property, created = IssueProperty.objects.get_or_create(
user=request.user,
project_id=project_id,
@ -463,7 +470,6 @@ class IssuePropertyViewSet(BaseViewSet):
class LabelViewSet(BaseViewSet):
serializer_class = LabelSerializer
model = Label
permission_classes = [
@ -490,14 +496,12 @@ class LabelViewSet(BaseViewSet):
class BulkDeleteIssuesEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def delete(self, request, slug, project_id):
try:
issue_ids = request.data.get("issue_ids", [])
if not len(issue_ids):
@ -527,14 +531,12 @@ class BulkDeleteIssuesEndpoint(BaseAPIView):
class SubIssuesEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get(self, request, slug, project_id, issue_id):
try:
sub_issues = (
Issue.objects.filter(
parent_id=issue_id, workspace__slug=slug, project_id=project_id

View file

@ -23,6 +23,7 @@ from .issue import (
IssueAssignee,
Label,
IssueBlocker,
IssueLink,
)
from .asset import FileAsset

View file

@ -161,6 +161,23 @@ class IssueAssignee(ProjectBaseModel):
return f"{self.issue.name} {self.assignee.email}"
class IssueLink(ProjectBaseModel):
title = models.CharField(max_length=255, null=True)
url = models.URLField()
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="issue_link"
)
class Meta:
verbose_name = "Issue Link"
verbose_name_plural = "Issue Links"
db_table = "issue_links"
ordering = ("-created_at",)
def __str__(self):
return f"{self.issue.name} {self.url}"
class IssueActivity(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="issue_activity"