[SILO-1158] chore: add context for project in relations API (#8860)

* add context for project in relations API

* modify issue relation serializer
This commit is contained in:
Saurabh Kumar
2026-04-20 15:29:28 +05:30
committed by GitHub
parent a8a16c8ba0
commit 45b4fc8932
2 changed files with 121 additions and 56 deletions
+25 -17
View File
@@ -480,44 +480,52 @@ class IssueLinkSerializer(BaseSerializer):
]
class IssueRelationRefSerializer(serializers.Serializer):
"""Project-scoped reference to a related work item."""
project_id = serializers.UUIDField(help_text="Project containing the related work item")
issue_id = serializers.UUIDField(help_text="ID of the related work item")
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.
Each list contains project_id and issue_id pairs so clients can resolve
cross-project relations.
"""
blocking = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that are blocking this issue",
child=IssueRelationRefSerializer(),
help_text="Work items blocking this issue",
)
blocked_by = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that this issue is blocked by",
child=IssueRelationRefSerializer(),
help_text="Work items this issue is blocked by",
)
duplicate = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that are duplicates of this issue",
child=IssueRelationRefSerializer(),
help_text="Duplicate work items",
)
relates_to = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that relate to this issue",
child=IssueRelationRefSerializer(),
help_text="Related work items",
)
start_after = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that start after this issue",
child=IssueRelationRefSerializer(),
help_text="Work items that start after this issue",
)
start_before = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that start before this issue",
child=IssueRelationRefSerializer(),
help_text="Work items that start before this issue",
)
finish_after = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that finish after this issue",
child=IssueRelationRefSerializer(),
help_text="Work items that finish after this issue",
)
finish_before = serializers.ListField(
child=serializers.UUIDField(),
help_text="List of issue IDs that finish before this issue",
child=IssueRelationRefSerializer(),
help_text="Work items that finish before this issue",
)
+96 -39
View File
@@ -23,11 +23,8 @@ from django.db.models import (
Value,
When,
Subquery,
UUIDField,
)
from django.db.models.functions import Coalesce
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.utils import timezone
from django.conf import settings
@@ -2292,14 +2289,35 @@ class IssueRelationListCreateAPIEndpoint(BaseAPIView):
name="Work Item Relations Response",
value={
"blocking": [
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001",
{
"project_id": "550e8400-e29b-41d4-a716-446655440010",
"issue_id": "550e8400-e29b-41d4-a716-446655440000",
},
{
"project_id": "550e8400-e29b-41d4-a716-446655440010",
"issue_id": "550e8400-e29b-41d4-a716-446655440001",
},
],
"blocked_by": [
{
"project_id": "550e8400-e29b-41d4-a716-446655440011",
"issue_id": "550e8400-e29b-41d4-a716-446655440002",
},
],
"blocked_by": ["550e8400-e29b-41d4-a716-446655440002"],
"duplicate": [],
"relates_to": ["550e8400-e29b-41d4-a716-446655440003"],
"relates_to": [
{
"project_id": "550e8400-e29b-41d4-a716-446655440010",
"issue_id": "550e8400-e29b-41d4-a716-446655440003",
},
],
"start_after": [],
"start_before": ["550e8400-e29b-41d4-a716-446655440004"],
"start_before": [
{
"project_id": "550e8400-e29b-41d4-a716-446655440012",
"issue_id": "550e8400-e29b-41d4-a716-446655440004",
},
],
"finish_after": [],
"finish_before": [],
},
@@ -2316,42 +2334,81 @@ class IssueRelationListCreateAPIEndpoint(BaseAPIView):
Retrieve all relationships for a work item organized by relation type.
Returns a structured response with relations grouped by type.
"""
empty_uuid_array = Value([], output_field=ArrayField(UUIDField()))
def _agg_ids(field, **filter_kwargs):
return Coalesce(
ArrayAgg(field, filter=Q(**filter_kwargs), distinct=True),
empty_uuid_array,
)
issue_relation_qs = IssueRelation.objects.filter(
relations = IssueRelation.objects.filter(
Q(issue_id=issue_id) | Q(related_issue_id=issue_id),
workspace__slug=slug,
)
relation_ids = issue_relation_qs.aggregate(
blocking_ids=_agg_ids("issue_id", relation_type="blocked_by", related_issue_id=issue_id),
blocked_by_ids=_agg_ids("related_issue_id", relation_type="blocked_by", issue_id=issue_id),
duplicate_ids=_agg_ids("related_issue_id", relation_type="duplicate", issue_id=issue_id),
duplicate_ids_related=_agg_ids("issue_id", relation_type="duplicate", related_issue_id=issue_id),
relates_to_ids=_agg_ids("related_issue_id", relation_type="relates_to", issue_id=issue_id),
relates_to_ids_related=_agg_ids("issue_id", relation_type="relates_to", related_issue_id=issue_id),
start_after_ids=_agg_ids("issue_id", relation_type="start_before", related_issue_id=issue_id),
start_before_ids=_agg_ids("related_issue_id", relation_type="start_before", issue_id=issue_id),
finish_after_ids=_agg_ids("issue_id", relation_type="finish_before", related_issue_id=issue_id),
finish_before_ids=_agg_ids("related_issue_id", relation_type="finish_before", issue_id=issue_id),
).values(
"relation_type",
"issue_id",
"related_issue_id",
issue_project_id=F("issue__project_id"),
related_issue_project_id=F("related_issue__project_id"),
)
response_data = {
"blocking": relation_ids["blocking_ids"],
"blocked_by": relation_ids["blocked_by_ids"],
"duplicate": list(set(relation_ids["duplicate_ids"] + relation_ids["duplicate_ids_related"])),
"relates_to": list(set(relation_ids["relates_to_ids"] + relation_ids["relates_to_ids_related"])),
"start_after": relation_ids["start_after_ids"],
"start_before": relation_ids["start_before_ids"],
"finish_after": relation_ids["finish_after_ids"],
"finish_before": relation_ids["finish_before_ids"],
"blocking": [],
"blocked_by": [],
"duplicate": [],
"relates_to": [],
"start_after": [],
"start_before": [],
"finish_after": [],
"finish_before": [],
}
seen_duplicate = set()
seen_relates_to = set()
for rel in relations:
rt = rel["relation_type"]
if rt == "blocked_by":
if str(rel["related_issue_id"]) == str(issue_id):
response_data["blocking"].append(
{"project_id": str(rel["issue_project_id"]), "issue_id": str(rel["issue_id"])}
)
if str(rel["issue_id"]) == str(issue_id):
response_data["blocked_by"].append(
{"project_id": str(rel["related_issue_project_id"]), "issue_id": str(rel["related_issue_id"])}
)
elif rt == "duplicate":
if str(rel["issue_id"]) == str(issue_id) and rel["related_issue_id"] not in seen_duplicate:
seen_duplicate.add(rel["related_issue_id"])
response_data["duplicate"].append(
{"project_id": str(rel["related_issue_project_id"]), "issue_id": str(rel["related_issue_id"])}
)
if str(rel["related_issue_id"]) == str(issue_id) and rel["issue_id"] not in seen_duplicate:
seen_duplicate.add(rel["issue_id"])
response_data["duplicate"].append(
{"project_id": str(rel["issue_project_id"]), "issue_id": str(rel["issue_id"])}
)
elif rt == "relates_to":
if str(rel["issue_id"]) == str(issue_id) and rel["related_issue_id"] not in seen_relates_to:
seen_relates_to.add(rel["related_issue_id"])
response_data["relates_to"].append(
{"project_id": str(rel["related_issue_project_id"]), "issue_id": str(rel["related_issue_id"])}
)
if str(rel["related_issue_id"]) == str(issue_id) and rel["issue_id"] not in seen_relates_to:
seen_relates_to.add(rel["issue_id"])
response_data["relates_to"].append(
{"project_id": str(rel["issue_project_id"]), "issue_id": str(rel["issue_id"])}
)
elif rt == "start_before":
if str(rel["related_issue_id"]) == str(issue_id):
response_data["start_after"].append(
{"project_id": str(rel["issue_project_id"]), "issue_id": str(rel["issue_id"])}
)
if str(rel["issue_id"]) == str(issue_id):
response_data["start_before"].append(
{"project_id": str(rel["related_issue_project_id"]), "issue_id": str(rel["related_issue_id"])}
)
elif rt == "finish_before":
if str(rel["related_issue_id"]) == str(issue_id):
response_data["finish_after"].append(
{"project_id": str(rel["issue_project_id"]), "issue_id": str(rel["issue_id"])}
)
if str(rel["issue_id"]) == str(issue_id):
response_data["finish_before"].append(
{"project_id": str(rel["related_issue_project_id"]), "issue_id": str(rel["related_issue_id"])}
)
return Response(response_data, status=status.HTTP_200_OK)