ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: realtime канал карточек задач

This commit is contained in:
DCCONSTRUCTIONS
2026-04-29 11:41:58 +03:00
parent 83c61a85b4
commit b2a710a7ec
13 changed files with 491 additions and 9 deletions
@@ -0,0 +1,3 @@
# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.
@@ -0,0 +1,47 @@
# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.
import json
import logging
from uuid import uuid4
from django.core.serializers.json import DjangoJSONEncoder
from django.db import transaction
from django.utils import timezone
from plane.settings.redis import redis_instance
logger = logging.getLogger(__name__)
ISSUE_EVENT_CHANNEL_PREFIX = "plane:issue-events:project"
def issue_event_channel(project_id):
return f"{ISSUE_EVENT_CHANNEL_PREFIX}:{project_id}"
def publish_issue_event_on_commit(event_type, issue, actor_id=None, changed_fields=None):
payload = {
"event_id": str(uuid4()),
"type": event_type,
"workspace_id": str(issue.workspace_id),
"workspace_slug": issue.workspace.slug if getattr(issue, "workspace", None) else None,
"project_id": str(issue.project_id),
"issue_id": str(issue.id),
"sequence_id": issue.sequence_id,
"updated_at": issue.updated_at or timezone.now(),
"actor_id": str(actor_id) if actor_id else None,
"changed_fields": sorted(set(changed_fields or [])),
}
def _publish():
try:
redis_instance().publish(
issue_event_channel(issue.project_id),
json.dumps(payload, cls=DjangoJSONEncoder),
)
except Exception:
logger.exception("Failed to publish issue realtime event")
transaction.on_commit(_publish)
@@ -33,6 +33,7 @@ from rest_framework.response import Response
# Module imports
from plane.app.permissions import ROLE, allow_permission
from plane.app.realtime.issue_events import publish_issue_event_on_commit
from plane.app.serializers import (
IssueCreateSerializer,
IssueDetailSerializer,
@@ -429,7 +430,7 @@ class IssueViewSet(BaseViewSet):
)
if serializer.is_valid():
serializer.save()
issue_instance = serializer.save()
# Track the issue
issue_activity.delay(
@@ -502,6 +503,12 @@ class IssueViewSet(BaseViewSet):
user_id=request.user.id,
is_creating=True,
)
publish_issue_event_on_commit(
"issue.created",
issue_instance,
actor_id=request.user.id,
changed_fields=request.data.keys(),
)
return Response(issue, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -695,7 +702,7 @@ class IssueViewSet(BaseViewSet):
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
serializer = IssueCreateSerializer(issue, data=request.data, partial=True, context={"project_id": project_id})
if serializer.is_valid():
serializer.save()
updated_issue = serializer.save()
# Check if the update is a migration description update
is_migration_description_update = skip_activity and is_description_update
# Log all the updates
@@ -726,12 +733,18 @@ class IssueViewSet(BaseViewSet):
issue_id=str(serializer.data.get("id", None)),
user_id=request.user.id,
)
publish_issue_event_on_commit(
"issue.updated",
updated_issue,
actor_id=request.user.id,
changed_fields=request.data.keys(),
)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@allow_permission([ROLE.ADMIN], creator=True, model=Issue)
def destroy(self, request, slug, project_id, pk=None):
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
issue = Issue.objects.select_related("workspace").get(workspace__slug=slug, project_id=project_id, pk=pk)
issue.delete()
# delete the issue from recent visits
@@ -753,6 +766,12 @@ class IssueViewSet(BaseViewSet):
origin=base_host(request=request, is_app=True),
subscriber=False,
)
publish_issue_event_on_commit(
"issue.deleted",
issue,
actor_id=request.user.id,
changed_fields=["deleted_at"],
)
return Response(status=status.HTTP_204_NO_CONTENT)