Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/api/plane/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
IssueCommentCreateSerializer,
IssueLinkCreateSerializer,
IssueLinkUpdateSerializer,
IssueRelationCreateSerializer,
IssueRelationResponseSerializer,
IssueRelationSerializer,
RelatedIssueSerializer,
)
from .state import StateLiteSerializer, StateSerializer
from .cycle import (
Expand Down
179 changes: 179 additions & 0 deletions apps/api/plane/api/serializers/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
IssueComment,
IssueLabel,
IssueLink,
IssueRelation,
Label,
ProjectMember,
State,
Expand Down Expand Up @@ -479,6 +480,184 @@ class Meta:
]


class IssueRelationResponseSerializer(serializers.Serializer):
"""
Serializer for issue relations response showing grouped relation types.

Returns issue IDs organized by relation type for efficient client-side processing.
"""

blocking = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that are blocking this issue",
)
blocked_by = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that this issue is blocked by",
)
duplicate = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that are duplicates of this issue",
)
relates_to = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that relate to this issue",
)
start_after = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that start after this issue",
)
start_before = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that start before this issue",
)
finish_after = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that finish after this issue",
)
finish_before = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that finish before this issue",
)


class IssueRelationCreateSerializer(serializers.Serializer):
"""
Serializer for creating issue relations.

Creates issue relations with the specified relation type and issues.
Validates relation types and ensures proper issue ID format.
"""

RELATION_TYPE_CHOICES = [
("blocking", "Blocking"),
("blocked_by", "Blocked By"),
("duplicate", "Duplicate"),
("relates_to", "Relates To"),
("start_before", "Start Before"),
("start_after", "Start After"),
("finish_before", "Finish Before"),
("finish_after", "Finish After"),
]

relation_type = serializers.ChoiceField(
choices=RELATION_TYPE_CHOICES,
required=True,
help_text="Type of relationship between work items",
)
issues = serializers.ListField(
child=serializers.UUIDField(),
required=True,
min_length=1,
help_text="Array of work item IDs to create relations with",
)

def validate_issues(self, value):
"""Validate that issues list is not empty and contains valid UUIDs."""
if not value:
raise serializers.ValidationError("At least one issue ID is required.")
return value


class IssueRelationRemoveSerializer(serializers.Serializer):
"""
Serializer for removing issue relations.

Removes existing relationships between work items by specifying
the related issue ID.
"""

related_issue = serializers.UUIDField(
required=True, help_text="ID of the related work item to remove relation with"
)


class IssueRelationSerializer(BaseSerializer):
"""
Serializer for issue relationships showing related issue details.

Provides comprehensive information about related issues including
project context, sequence ID, and relationship type.
"""

id = serializers.UUIDField(source="related_issue.id", read_only=True)
project_id = serializers.UUIDField(source="related_issue.project_id", read_only=True)
sequence_id = serializers.IntegerField(source="related_issue.sequence_id", read_only=True)
name = serializers.CharField(source="related_issue.name", read_only=True)
relation_type = serializers.CharField(read_only=True)
state_id = serializers.UUIDField(source="related_issue.state.id", read_only=True)
priority = serializers.CharField(source="related_issue.priority", read_only=True)

class Meta:
model = IssueRelation
fields = [
"id",
"project_id",
"sequence_id",
"relation_type",
"name",
"state_id",
"priority",
"created_by",
"created_at",
"updated_at",
"updated_by",
]
read_only_fields = [
"workspace",
"project",
"created_by",
"created_at",
"updated_by",
"updated_at",
]


class RelatedIssueSerializer(BaseSerializer):
"""
Serializer for reverse issue relationships showing issue details.

Provides comprehensive information about the source issue in a relationship
including project context, sequence ID, and relationship type.
"""

id = serializers.UUIDField(source="issue.id", read_only=True)
project_id = serializers.PrimaryKeyRelatedField(source="issue.project_id", read_only=True)
sequence_id = serializers.IntegerField(source="issue.sequence_id", read_only=True)
name = serializers.CharField(source="issue.name", read_only=True)
type_id = serializers.UUIDField(source="issue.type.id", read_only=True)
relation_type = serializers.CharField(read_only=True)
is_epic = serializers.BooleanField(source="issue.type.is_epic", read_only=True)
state_id = serializers.UUIDField(source="issue.state.id", read_only=True)
priority = serializers.CharField(source="issue.priority", read_only=True)

class Meta:
model = IssueRelation
fields = [
"id",
"project_id",
"sequence_id",
"relation_type",
"name",
"type_id",
"is_epic",
"state_id",
"priority",
"created_by",
"created_at",
"updated_by",
"updated_at",
]
read_only_fields = [
"workspace",
"project",
"created_by",
"created_at",
"updated_by",
"updated_at",
]


class IssueAttachmentSerializer(BaseSerializer):
"""
Serializer for work item file attachments.
Expand Down
6 changes: 6 additions & 0 deletions apps/api/plane/api/urls/work_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
IssueAttachmentDetailAPIEndpoint,
WorkspaceIssueAPIEndpoint,
IssueSearchEndpoint,
IssueRelationListCreateAPIEndpoint,
)

# Deprecated url patterns
Expand Down Expand Up @@ -145,6 +146,11 @@
IssueAttachmentDetailAPIEndpoint.as_view(http_method_names=["get", "patch", "delete"]),
name="work-item-attachment-detail",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/work-items/<uuid:issue_id>/relations/",
IssueRelationListCreateAPIEndpoint.as_view(http_method_names=["get", "post"]),
name="work-item-relation-list",
),
]

urlpatterns = old_url_patterns + new_url_patterns
1 change: 1 addition & 0 deletions apps/api/plane/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
IssueAttachmentListCreateAPIEndpoint,
IssueAttachmentDetailAPIEndpoint,
IssueSearchEndpoint,
IssueRelationListCreateAPIEndpoint,
)

from .cycle import (
Expand Down
Loading
Loading