mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
dev: update webhook logic for issues
This commit is contained in:
parent
aa09ec7cd4
commit
fe68e14d8b
@ -52,8 +52,7 @@ from plane.db.models import (
|
|||||||
from .base import BaseAPIView, WebhookMixin
|
from .base import BaseAPIView, WebhookMixin
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceIssueAPIEndpoint(BaseAPIView):
|
||||||
class WorkspaceIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|
||||||
"""
|
"""
|
||||||
This viewset provides `retrieveByIssueId` on workspace level
|
This viewset provides `retrieveByIssueId` on workspace level
|
||||||
|
|
||||||
@ -61,12 +60,9 @@ class WorkspaceIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
|
|
||||||
model = Issue
|
model = Issue
|
||||||
webhook_event = "issue"
|
webhook_event = "issue"
|
||||||
permission_classes = [
|
permission_classes = [ProjectEntityPermission]
|
||||||
ProjectEntityPermission
|
|
||||||
]
|
|
||||||
serializer_class = IssueSerializer
|
serializer_class = IssueSerializer
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def project__identifier(self):
|
def project__identifier(self):
|
||||||
return self.kwargs.get("project__identifier", None)
|
return self.kwargs.get("project__identifier", None)
|
||||||
@ -92,7 +88,9 @@ class WorkspaceIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
.order_by(self.kwargs.get("order_by", "-created_at"))
|
.order_by(self.kwargs.get("order_by", "-created_at"))
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
def get(self, request, slug, project__identifier=None, issue__identifier=None):
|
def get(
|
||||||
|
self, request, slug, project__identifier=None, issue__identifier=None
|
||||||
|
):
|
||||||
if issue__identifier and project__identifier:
|
if issue__identifier and project__identifier:
|
||||||
issue = Issue.issue_objects.annotate(
|
issue = Issue.issue_objects.annotate(
|
||||||
sub_issues_count=Issue.issue_objects.filter(
|
sub_issues_count=Issue.issue_objects.filter(
|
||||||
@ -101,7 +99,11 @@ class WorkspaceIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
.order_by()
|
.order_by()
|
||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
).get(workspace__slug=slug, project__identifier=project__identifier, sequence_id=issue__identifier)
|
).get(
|
||||||
|
workspace__slug=slug,
|
||||||
|
project__identifier=project__identifier,
|
||||||
|
sequence_id=issue__identifier,
|
||||||
|
)
|
||||||
return Response(
|
return Response(
|
||||||
IssueSerializer(
|
IssueSerializer(
|
||||||
issue,
|
issue,
|
||||||
@ -111,7 +113,8 @@ class WorkspaceIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|
||||||
|
class IssueAPIEndpoint(BaseAPIView):
|
||||||
"""
|
"""
|
||||||
This viewset automatically provides `list`, `create`, `retrieve`,
|
This viewset automatically provides `list`, `create`, `retrieve`,
|
||||||
`update` and `destroy` actions related to issue.
|
`update` and `destroy` actions related to issue.
|
||||||
|
@ -249,6 +249,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
update_cycle_issue_activity = []
|
update_cycle_issue_activity = []
|
||||||
# Iterate over each cycle_issue in cycle_issues
|
# Iterate over each cycle_issue in cycle_issues
|
||||||
for 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
|
# Update the cycle_issue's cycle_id
|
||||||
cycle_issue.cycle_id = cycle_id
|
cycle_issue.cycle_id = cycle_id
|
||||||
# Add the modified cycle_issue to the records_to_update list
|
# Add the modified cycle_issue to the records_to_update list
|
||||||
@ -256,7 +257,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
# Record the update activity
|
# Record the update activity
|
||||||
update_cycle_issue_activity.append(
|
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),
|
"new_cycle_id": str(cycle_id),
|
||||||
"issue_id": str(cycle_issue.issue_id),
|
"issue_id": str(cycle_issue.issue_id),
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ from plane.db.models import (
|
|||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .. import BaseAPIView, BaseViewSet, WebhookMixin
|
from .. import BaseAPIView, BaseViewSet
|
||||||
|
|
||||||
|
|
||||||
class IssueListEndpoint(BaseAPIView):
|
class IssueListEndpoint(BaseAPIView):
|
||||||
@ -244,7 +244,7 @@ class IssueListEndpoint(BaseAPIView):
|
|||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class IssueViewSet(WebhookMixin, BaseViewSet):
|
class IssueViewSet(BaseViewSet):
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
return (
|
return (
|
||||||
IssueCreateSerializer
|
IssueCreateSerializer
|
||||||
|
@ -31,6 +31,7 @@ from plane.db.models import (
|
|||||||
)
|
)
|
||||||
from plane.settings.redis import redis_instance
|
from plane.settings.redis import redis_instance
|
||||||
from plane.utils.exception_logger import log_exception
|
from plane.utils.exception_logger import log_exception
|
||||||
|
from plane.bgtasks.webhook_task import webhook_activity
|
||||||
|
|
||||||
|
|
||||||
# Track Changes in name
|
# Track Changes in name
|
||||||
@ -1692,6 +1693,19 @@ def issue_activity(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_exception(e)
|
log_exception(e)
|
||||||
|
|
||||||
|
for activity in issue_activities_created:
|
||||||
|
webhook_activity.delay(
|
||||||
|
event="issue",
|
||||||
|
event_id=activity.issue_id,
|
||||||
|
verb=activity.verb,
|
||||||
|
field=activity.field,
|
||||||
|
old_value=activity.old_value,
|
||||||
|
new_value=activity.new_value,
|
||||||
|
actor_id=activity.actor_id,
|
||||||
|
current_site=origin,
|
||||||
|
slug=activity.workspace.slug,
|
||||||
|
)
|
||||||
|
|
||||||
if notification:
|
if notification:
|
||||||
notifications.delay(
|
notifications.delay(
|
||||||
type=type,
|
type=type,
|
||||||
|
@ -294,3 +294,169 @@ def send_webhook_deactivation_email(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_exception(e)
|
log_exception(e)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(
|
||||||
|
bind=True,
|
||||||
|
autoretry_for=(requests.RequestException,),
|
||||||
|
retry_backoff=600,
|
||||||
|
max_retries=5,
|
||||||
|
retry_jitter=True,
|
||||||
|
)
|
||||||
|
def webhook_send_task(
|
||||||
|
self,
|
||||||
|
webhook,
|
||||||
|
slug,
|
||||||
|
event,
|
||||||
|
event_data,
|
||||||
|
action,
|
||||||
|
current_site,
|
||||||
|
activity,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
webhook = Webhook.objects.get(id=webhook, workspace__slug=slug)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "Autopilot",
|
||||||
|
"X-Plane-Delivery": str(uuid.uuid4()),
|
||||||
|
"X-Plane-Event": event,
|
||||||
|
}
|
||||||
|
|
||||||
|
# # Your secret key
|
||||||
|
event_data = (
|
||||||
|
json.loads(json.dumps(event_data, cls=DjangoJSONEncoder))
|
||||||
|
if event_data is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
action = {
|
||||||
|
"POST": "create",
|
||||||
|
"PATCH": "update",
|
||||||
|
"PUT": "update",
|
||||||
|
"DELETE": "delete",
|
||||||
|
}.get(action, action)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"event": event,
|
||||||
|
"action": action,
|
||||||
|
"webhook_id": str(webhook.id),
|
||||||
|
"workspace_id": str(webhook.workspace_id),
|
||||||
|
"data": event_data,
|
||||||
|
"activity": activity,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use HMAC for generating signature
|
||||||
|
if webhook.secret_key:
|
||||||
|
hmac_signature = hmac.new(
|
||||||
|
webhook.secret_key.encode("utf-8"),
|
||||||
|
json.dumps(payload).encode("utf-8"),
|
||||||
|
hashlib.sha256,
|
||||||
|
)
|
||||||
|
signature = hmac_signature.hexdigest()
|
||||||
|
headers["X-Plane-Signature"] = signature
|
||||||
|
|
||||||
|
# Send the webhook event
|
||||||
|
response = requests.post(
|
||||||
|
webhook.url,
|
||||||
|
headers=headers,
|
||||||
|
json=payload,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log the webhook request
|
||||||
|
WebhookLog.objects.create(
|
||||||
|
workspace_id=str(webhook.workspace_id),
|
||||||
|
webhook_id=str(webhook.id),
|
||||||
|
event_type=str(event),
|
||||||
|
request_method=str(action),
|
||||||
|
request_headers=str(headers),
|
||||||
|
request_body=str(payload),
|
||||||
|
response_status=str(response.status_code),
|
||||||
|
response_headers=str(response.headers),
|
||||||
|
response_body=str(response.text),
|
||||||
|
retry_count=str(self.request.retries),
|
||||||
|
)
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
# Log the failed webhook request
|
||||||
|
WebhookLog.objects.create(
|
||||||
|
workspace_id=str(webhook.workspace_id),
|
||||||
|
webhook_id=str(webhook.id),
|
||||||
|
event_type=str(event),
|
||||||
|
request_method=str(action),
|
||||||
|
request_headers=str(headers),
|
||||||
|
request_body=str(payload),
|
||||||
|
response_status=500,
|
||||||
|
response_headers="",
|
||||||
|
response_body=str(e),
|
||||||
|
retry_count=str(self.request.retries),
|
||||||
|
)
|
||||||
|
# Retry logic
|
||||||
|
if self.request.retries >= self.max_retries:
|
||||||
|
Webhook.objects.filter(pk=webhook.id).update(is_active=False)
|
||||||
|
if webhook:
|
||||||
|
# send email for the deactivation of the webhook
|
||||||
|
send_webhook_deactivation_email(
|
||||||
|
webhook_id=webhook.id,
|
||||||
|
receiver_id=webhook.created_by_id,
|
||||||
|
reason=str(e),
|
||||||
|
current_site=current_site,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
raise requests.RequestException()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if settings.DEBUG:
|
||||||
|
print(e)
|
||||||
|
log_exception(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def webhook_activity(
|
||||||
|
event,
|
||||||
|
verb,
|
||||||
|
field,
|
||||||
|
old_value,
|
||||||
|
new_value,
|
||||||
|
actor_id,
|
||||||
|
slug,
|
||||||
|
current_site,
|
||||||
|
event_id,
|
||||||
|
):
|
||||||
|
webhooks = Webhook.objects.filter(workspace__slug=slug, is_active=True)
|
||||||
|
|
||||||
|
if event == "project":
|
||||||
|
webhooks = webhooks.filter(project=True)
|
||||||
|
|
||||||
|
if event == "issue":
|
||||||
|
webhooks = webhooks.filter(issue=True)
|
||||||
|
|
||||||
|
if event == "module" or event == "module_issue":
|
||||||
|
webhooks = webhooks.filter(module=True)
|
||||||
|
|
||||||
|
if event == "cycle" or event == "cycle_issue":
|
||||||
|
webhooks = webhooks.filter(cycle=True)
|
||||||
|
|
||||||
|
if event == "issue_comment":
|
||||||
|
webhooks = webhooks.filter(issue_comment=True)
|
||||||
|
|
||||||
|
for webhook in webhooks:
|
||||||
|
webhook_send_task.delay(
|
||||||
|
webhook=webhook.id,
|
||||||
|
slug=slug,
|
||||||
|
event=event,
|
||||||
|
event_data=get_model_data(
|
||||||
|
event=event,
|
||||||
|
event_id=event_id,
|
||||||
|
),
|
||||||
|
action=verb,
|
||||||
|
current_site=current_site,
|
||||||
|
activity={
|
||||||
|
"field": field,
|
||||||
|
"new_value": new_value,
|
||||||
|
"old_value": old_value,
|
||||||
|
"actor_id": actor_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user