[WEB - 1122] fix: webhook for issues, issue comments, projects, cycles and modules. (#4330)

* dev: update webhook logic for issues

* dev: update issue webhooks for cycle and module

* dev: webhook for comment

* dev: issue attachment webhooks

* dev: add logging

* dev: add inbox issue webhooks

* dev: update the webhook send task

* dev: project webhooks for api

* dev: webhooks update for projects, cycles and modules

* dev: fix webhook on cycle and module create from external apis
This commit is contained in:
Nikhil 2024-05-06 14:13:49 +05:30 committed by GitHub
parent fb74875cde
commit f1fda4ae4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 545 additions and 191 deletions

View file

@ -30,7 +30,7 @@ from .user.base import (
from .oauth import OauthEndpoint
from .base import BaseAPIView, BaseViewSet, WebhookMixin
from .base import BaseAPIView, BaseViewSet
from .workspace.base import (
WorkSpaceViewSet,

View file

@ -19,7 +19,6 @@ from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
# Module imports
from plane.bgtasks.webhook_task import send_webhook
from plane.utils.exception_logger import log_exception
from plane.utils.paginator import BasePaginator
@ -38,35 +37,6 @@ class TimezoneMixin:
timezone.deactivate()
class WebhookMixin:
webhook_event = None
bulk = False
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(
request, response, *args, **kwargs
)
# Check for the case should webhook be sent
if (
self.webhook_event
and self.request.method in ["POST", "PATCH", "DELETE"]
and response.status_code in [200, 201, 204]
):
# Push the object to delay
send_webhook.delay(
event=self.webhook_event,
payload=response.data,
kw=self.kwargs,
action=self.request.method,
slug=self.workspace_slug,
bulk=self.bulk,
current_site=request.META.get("HTTP_ORIGIN"),
)
return response
class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
model = None

View file

@ -20,6 +20,7 @@ from django.db.models import (
)
from django.db.models.functions import Coalesce
from django.utils import timezone
from django.core.serializers.json import DjangoJSONEncoder
# Third party imports
from rest_framework import status
@ -47,10 +48,11 @@ from plane.db.models import (
from plane.utils.analytics_plot import burndown_plot
# Module imports
from .. import BaseAPIView, BaseViewSet, WebhookMixin
from .. import BaseAPIView, BaseViewSet
from plane.bgtasks.webhook_task import model_activity
class CycleViewSet(WebhookMixin, BaseViewSet):
class CycleViewSet(BaseViewSet):
serializer_class = CycleSerializer
model = Cycle
webhook_event = "cycle"
@ -412,6 +414,17 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.first()
)
# Send the model activity
model_activity.delay(
model_name="cycle",
model_id=str(cycle["id"]),
requested_data=request.data,
current_instance=None,
actor_id=request.user.id,
slug=slug,
origin=request.META.get("HTTP_ORIGIN"),
)
return Response(cycle, status=status.HTTP_201_CREATED)
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
@ -434,6 +447,11 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
{"error": "Archived cycle cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
current_instance = json.dumps(
CycleSerializer(cycle).data, cls=DjangoJSONEncoder
)
request_data = request.data
if (
@ -487,6 +505,18 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
"assignee_ids",
"status",
).first()
# Send the model activity
model_activity.delay(
model_name="cycle",
model_id=str(cycle["id"]),
requested_data=request.data,
current_instance=current_instance,
actor_id=request.user.id,
slug=slug,
origin=request.META.get("HTTP_ORIGIN"),
)
return Response(cycle, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View file

@ -23,7 +23,7 @@ from rest_framework.response import Response
from rest_framework import status
# Module imports
from .. import BaseViewSet, WebhookMixin
from .. import BaseViewSet
from plane.app.serializers import (
IssueSerializer,
CycleIssueSerializer,
@ -40,7 +40,7 @@ from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.issue_filters import issue_filters
from plane.utils.user_timezone_converter import user_timezone_converter
class CycleIssueViewSet(WebhookMixin, BaseViewSet):
class CycleIssueViewSet(BaseViewSet):
serializer_class = CycleIssueSerializer
model = CycleIssue
@ -254,6 +254,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
update_cycle_issue_activity = []
# Iterate over each cycle_issue in cycle_issues
for cycle_issue in cycle_issues:
old_cycle_id = cycle_issue.cycle_id
# Update the cycle_issue's cycle_id
cycle_issue.cycle_id = cycle_id
# Add the modified cycle_issue to the records_to_update list
@ -261,7 +262,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
# Record the update activity
update_cycle_issue_activity.append(
{
"old_cycle_id": str(cycle_issue.cycle_id),
"old_cycle_id": str(old_cycle_id),
"new_cycle_id": str(cycle_id),
"issue_id": str(cycle_issue.issue_id),
}

View file

@ -251,6 +251,16 @@ class InboxIssueViewSet(BaseViewSet):
)
if serializer.is_valid():
serializer.save()
inbox_id = Inbox.objects.filter(
workspace__slug=slug, project_id=project_id
).first()
# create an inbox issue
inbox_issue = InboxIssue.objects.create(
inbox_id=inbox_id.id,
project_id=project_id,
issue_id=serializer.data["id"],
source=request.data.get("source", "in-app"),
)
# Create an Issue Activity
issue_activity.delay(
type="issue.activity.created",
@ -262,16 +272,7 @@ class InboxIssueViewSet(BaseViewSet):
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
inbox_id = Inbox.objects.filter(
workspace__slug=slug, project_id=project_id
).first()
# create an inbox issue
inbox_issue = InboxIssue.objects.create(
inbox_id=inbox_id.id,
project_id=project_id,
issue_id=serializer.data["id"],
source=request.data.get("source", "in-app"),
inbox=str(inbox_issue.id),
)
inbox_issue = (
InboxIssue.objects.select_related("issue")
@ -339,7 +340,24 @@ class InboxIssueViewSet(BaseViewSet):
# Get issue data
issue_data = request.data.pop("issue", False)
if bool(issue_data):
issue = Issue.objects.get(
issue = Issue.objects.annotate(
label_ids=Coalesce(
ArrayAgg(
"labels__id",
distinct=True,
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
assignee_ids=Coalesce(
ArrayAgg(
"assignees__id",
distinct=True,
filter=~Q(assignees__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
).get(
pk=inbox_issue.issue_id,
workspace__slug=slug,
project_id=project_id,
@ -379,6 +397,7 @@ class InboxIssueViewSet(BaseViewSet):
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
inbox=str(inbox_issue.id),
)
issue_serializer.save()
else:
@ -444,6 +463,7 @@ class InboxIssueViewSet(BaseViewSet):
epoch=int(timezone.now().timestamp()),
notification=False,
origin=request.META.get("HTTP_ORIGIN"),
inbox=(inbox_issue.id),
)
inbox_issue = (
@ -480,7 +500,8 @@ class InboxIssueViewSet(BaseViewSet):
output_field=ArrayField(UUIDField()),
),
),
).first()
)
.first()
)
serializer = InboxIssueDetailSerializer(inbox_issue).data
return Response(serializer, status=status.HTTP_200_OK)

View file

@ -53,7 +53,7 @@ from plane.utils.issue_filters import issue_filters
from plane.utils.user_timezone_converter import user_timezone_converter
# Module imports
from .. import BaseAPIView, BaseViewSet, WebhookMixin
from .. import BaseAPIView, BaseViewSet
class IssueListEndpoint(BaseAPIView):
@ -249,7 +249,7 @@ class IssueListEndpoint(BaseAPIView):
return Response(issues, status=status.HTTP_200_OK)
class IssueViewSet(WebhookMixin, BaseViewSet):
class IssueViewSet(BaseViewSet):
def get_serializer_class(self):
return (
IssueCreateSerializer

View file

@ -11,7 +11,7 @@ from rest_framework.response import Response
from rest_framework import status
# Module imports
from .. import BaseViewSet, WebhookMixin
from .. import BaseViewSet
from plane.app.serializers import (
IssueCommentSerializer,
CommentReactionSerializer,
@ -25,7 +25,7 @@ from plane.db.models import (
from plane.bgtasks.issue_activites_task import issue_activity
class IssueCommentViewSet(WebhookMixin, BaseViewSet):
class IssueCommentViewSet(BaseViewSet):
serializer_class = IssueCommentSerializer
model = IssueComment
webhook_event = "issue_comment"

View file

@ -1,6 +1,7 @@
# Python imports
import json
# Django Imports
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.db.models import (
@ -17,14 +18,14 @@ from django.db.models import (
Value,
)
from django.db.models.functions import Coalesce
# Django Imports
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone
from rest_framework import status
# Third party imports
from rest_framework import status
from rest_framework.response import Response
# Module imports
from plane.app.permissions import (
ProjectEntityPermission,
ProjectLitePermission,
@ -49,13 +50,11 @@ from plane.db.models import (
)
from plane.utils.analytics_plot import burndown_plot
from plane.utils.user_timezone_converter import user_timezone_converter
from plane.bgtasks.webhook_task import model_activity
from .. import BaseAPIView, BaseViewSet
# Module imports
from .. import BaseAPIView, BaseViewSet, WebhookMixin
class ModuleViewSet(WebhookMixin, BaseViewSet):
class ModuleViewSet(BaseViewSet):
model = Module
permission_classes = [
ProjectEntityPermission,
@ -238,6 +237,16 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
"updated_at",
)
).first()
# Send the model activity
model_activity.delay(
model_name="module",
model_id=str(module["id"]),
requested_data=request.data,
current_instance=None,
actor_id=request.user.id,
slug=slug,
origin=request.META.get("HTTP_ORIGIN"),
)
datetime_fields = ["created_at", "updated_at"]
module = user_timezone_converter(
module, datetime_fields, request.user.user_timezone
@ -422,6 +431,9 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
def partial_update(self, request, slug, project_id, pk):
module = self.get_queryset().filter(pk=pk)
current_instance = json.dumps(
ModuleSerializer(module).data, cls=DjangoJSONEncoder
)
if module.first().archived_at:
return Response(
@ -464,6 +476,18 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
"created_at",
"updated_at",
).first()
# Send the model activity
model_activity.delay(
model_name="module",
model_id=str(module["id"]),
requested_data=request.data,
current_instance=current_instance,
actor_id=request.user.id,
slug=slug,
origin=request.META.get("HTTP_ORIGIN"),
)
datetime_fields = ["created_at", "updated_at"]
module = user_timezone_converter(
module, datetime_fields, request.user.user_timezone

View file

@ -16,7 +16,7 @@ from rest_framework.response import Response
from rest_framework import status
# Module imports
from .. import BaseViewSet, WebhookMixin
from .. import BaseViewSet
from plane.app.serializers import (
ModuleIssueSerializer,
IssueSerializer,
@ -33,7 +33,7 @@ from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.issue_filters import issue_filters
from plane.utils.user_timezone_converter import user_timezone_converter
class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
class ModuleIssueViewSet(BaseViewSet):
serializer_class = ModuleIssueSerializer
model = ModuleIssue
webhook_event = "module_issue"

View file

@ -1,5 +1,6 @@
# Python imports
import boto3
import json
# Django imports
from django.db import IntegrityError
@ -14,6 +15,7 @@ from django.db.models import (
)
from django.conf import settings
from django.utils import timezone
from django.core.serializers.json import DjangoJSONEncoder
# Third Party imports
from rest_framework.response import Response
@ -22,7 +24,7 @@ from rest_framework import serializers
from rest_framework.permissions import AllowAny
# Module imports
from plane.app.views.base import BaseViewSet, BaseAPIView, WebhookMixin
from plane.app.views.base import BaseViewSet, BaseAPIView
from plane.app.serializers import (
ProjectSerializer,
ProjectListSerializer,
@ -50,9 +52,10 @@ from plane.db.models import (
Issue,
)
from plane.utils.cache import cache_response
from plane.bgtasks.webhook_task import model_activity
class ProjectViewSet(WebhookMixin, BaseViewSet):
class ProjectViewSet(BaseViewSet):
serializer_class = ProjectListSerializer
model = Project
webhook_event = "project"
@ -334,6 +337,17 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
.filter(pk=serializer.data["id"])
.first()
)
model_activity.delay(
model_name="project",
model_id=str(project.id),
requested_data=request.data,
current_instance=None,
actor_id=request.user.id,
slug=slug,
origin=request.META.get("HTTP_ORIGIN"),
)
serializer = ProjectListSerializer(project)
return Response(
serializer.data, status=status.HTTP_201_CREATED
@ -364,7 +378,9 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
workspace = Workspace.objects.get(slug=slug)
project = Project.objects.get(pk=pk)
current_instance = json.dumps(
ProjectSerializer(project).data, cls=DjangoJSONEncoder
)
if project.archived_at:
return Response(
{"error": "Archived projects cannot be updated"},
@ -402,6 +418,16 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
.filter(pk=serializer.data["id"])
.first()
)
model_activity.delay(
model_name="project",
model_id=str(project.id),
requested_data=request.data,
current_instance=current_instance,
actor_id=request.user.id,
slug=slug,
origin=request.META.get("HTTP_ORIGIN"),
)
serializer = ProjectListSerializer(project)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(