ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: source-side принятие и возврат внешнего запроса

This commit is contained in:
DCCONSTRUCTIONS
2026-04-19 00:29:23 +03:00
parent 3fe3539614
commit 8195c3fc80
17 changed files with 366 additions and 23 deletions
@@ -3,11 +3,13 @@
# See the LICENSE file for details.
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import status
from rest_framework.response import Response
from plane.api.serializers import (
ExternalContourRequestCreateSerializer,
ExternalContourRequestDecisionSerializer,
ExternalContourRequestSerializer,
ExternalContourTargetOptionsSerializer,
ExternalContourTargetProjectSerializer,
@@ -26,6 +28,15 @@ class ExternalContourListCreateEndpoint(BaseAPIView):
def get_source_project(self, slug, project_id):
return get_object_or_404(Project, workspace__slug=slug, pk=project_id)
def get_default_target_state(self, target_project):
return (
State.objects.filter(project=target_project, default=True)
.exclude(group=StateGroup.TRIAGE.value)
.first()
) or State.objects.filter(project=target_project).exclude(group=StateGroup.TRIAGE.value).order_by(
"sequence", "created_at"
).first()
def get_queryset(self):
return (
IntakeIssue.objects.filter(
@@ -93,11 +104,7 @@ class ExternalContourListCreateEndpoint(BaseAPIView):
default=False,
)
target_default_state = (
State.objects.filter(project=target_project, default=True)
.exclude(group=StateGroup.TRIAGE.value)
.first()
) or State.objects.filter(project=target_project).exclude(group=StateGroup.TRIAGE.value).order_by("sequence", "created_at").first()
target_default_state = self.get_default_target_state(target_project)
if not target_default_state:
return Response({"error": "Target project has no available workflow state"}, status=status.HTTP_400_BAD_REQUEST)
@@ -264,3 +271,84 @@ class ExternalContourDetailEndpoint(BaseAPIView):
contour_request = get_object_or_404(self.get_queryset())
serializer = ExternalContourRequestSerializer(contour_request)
return Response(serializer.data, status=status.HTTP_200_OK)
class ExternalContourDecisionEndpoint(BaseAPIView):
permission_classes = [ProjectLitePermission]
serializer_class = ExternalContourRequestSerializer
def get_queryset(self):
return (
IntakeIssue.objects.filter(
workspace__slug=self.kwargs.get("slug"),
extra__bridge="external-contours",
extra__source_project_id=str(self.kwargs.get("project_id")),
pk=self.kwargs.get("request_id"),
)
.select_related(
"issue",
"issue__state",
"issue__project",
"issue__created_by",
)
.prefetch_related("issue__issue_assignee__assignee", "issue__label_issue__label")
)
def post(self, request, slug, project_id, request_id):
contour_request = get_object_or_404(self.get_queryset())
serializer = ExternalContourRequestDecisionSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
action = serializer.validated_data["action"]
issue = contour_request.issue
if not issue or not issue.state or issue.state.group not in [StateGroup.COMPLETED.value, StateGroup.CANCELLED.value]:
return Response(
{"error": "Source decision is available only after the target contour finishes processing"},
status=status.HTTP_400_BAD_REQUEST,
)
if action == "accept":
contour_request.extra = {
**contour_request.extra,
"source_decision": "accepted",
"source_decision_at": timezone.now().isoformat(),
"source_decision_by_name": request.user.display_name,
}
contour_request.save(update_fields=["extra", "updated_at"])
else:
target_default_state = (
State.objects.filter(project=issue.project, default=True)
.exclude(group=StateGroup.TRIAGE.value)
.first()
) or State.objects.filter(project=issue.project).exclude(group=StateGroup.TRIAGE.value).order_by(
"sequence", "created_at"
).first()
if not target_default_state:
return Response({"error": "Target project has no available workflow state"}, status=status.HTTP_400_BAD_REQUEST)
issue.state = target_default_state
issue.save(update_fields=["state", "updated_at"])
extra = dict(contour_request.extra or {})
extra.pop("source_decision", None)
extra.pop("source_decision_at", None)
extra.pop("source_decision_by_name", None)
extra["last_reopened_at"] = issue.updated_at.isoformat() if issue.updated_at else None
extra["last_reopened_by_name"] = request.user.display_name
contour_request.extra = extra
contour_request.save(update_fields=["extra", "updated_at"])
contour_request.refresh_from_db()
serializer = ExternalContourRequestSerializer(
IntakeIssue.objects.select_related(
"issue",
"issue__state",
"issue__project",
"issue__created_by",
)
.prefetch_related("issue__issue_assignee__assignee", "issue__label_issue__label")
.get(pk=contour_request.id)
)
return Response(serializer.data, status=status.HTTP_200_OK)