diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 085bb9bd1..381891f2f 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -75,4 +75,7 @@ from .estimate import ( ) from .inbox import InboxSerializer, InboxIssueSerializer, IssueStateInboxSerializer + from .analytic import AnalyticViewSerializer + +from .notification import NotificationSerializer diff --git a/apiserver/plane/api/serializers/notification.py b/apiserver/plane/api/serializers/notification.py new file mode 100644 index 000000000..529cb9f9c --- /dev/null +++ b/apiserver/plane/api/serializers/notification.py @@ -0,0 +1,10 @@ +# Module imports +from .base import BaseSerializer +from plane.db.models import Notification + +class NotificationSerializer(BaseSerializer): + + class Meta: + model = Notification + fields = "__all__" + diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index bf370063a..f871f6f3d 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -149,6 +149,9 @@ from plane.api.views import ( ExportAnalyticsEndpoint, DefaultAnalyticsEndpoint, ## End Analytics + # Notification + NotificationViewSet, + ## End Notification ) @@ -803,11 +806,7 @@ urlpatterns = [ path( "workspaces//projects//issues//subscribers/", IssueSubscriberViewSet.as_view( - { - "get": "list", - "post": "create", - "delete": "destroy" - } + {"get": "list", "post": "create", "delete": "destroy"} ), name="project-issue-subscriber", ), @@ -1288,4 +1287,26 @@ urlpatterns = [ name="default-analytics", ), ## End Analytics + # Notification + path( + "workspaces//users/notifications/", + NotificationViewSet.as_view( + { + "get": "list", + } + ), + name="notifications", + ), + path( + "workspaces//users/notifications//", + NotificationViewSet.as_view( + { + "get": "retrieve", + "patch": "partial_update", + "delete": "destroy", + } + ), + name="notifications", + ), + ## End Notification ] diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index a3c166e80..327dd6037 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -134,6 +134,7 @@ from .estimate import ( from .release import ReleaseNotesEndpoint from .inbox import InboxViewSet, InboxIssueViewSet + from .analytic import ( AnalyticsEndpoint, AnalyticViewViewset, @@ -141,3 +142,5 @@ from .analytic import ( ExportAnalyticsEndpoint, DefaultAnalyticsEndpoint, ) + +from .notification import NotificationViewSet \ No newline at end of file diff --git a/apiserver/plane/api/views/notification.py b/apiserver/plane/api/views/notification.py new file mode 100644 index 000000000..add3c6994 --- /dev/null +++ b/apiserver/plane/api/views/notification.py @@ -0,0 +1,24 @@ +# Third party imports +from rest_framework import status +from rest_framework.response import Response + +# Module imports +from .base import BaseViewSet +from plane.db.models import Notification +from plane.api.serializers import NotificationSerializer + + +class NotificationViewSet(BaseViewSet): + model = Notification + serializer_class = NotificationSerializer + + def get_queryset(self): + return ( + super() + .get_queryset() + .filter( + workspace__slug=self.kwargs.get("slug"), + ) + .select_related("workspace") + ) + diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index b1096e30b..9fa0d2fac 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -20,8 +20,10 @@ from plane.db.models import ( State, Cycle, Module, + IssueSubscriber, + Notification, ) -from plane.api.serializers import IssueActivitySerializer +from plane.api.serializers import IssueActivitySerializer, IssueFlatSerializer # Track Chnages in name @@ -992,18 +994,57 @@ def issue_activity( # Post the updates to segway for integrations and webhooks if len(issue_activities_created): # Don't send activities if the actor is a bot - if settings.PROXY_BASE_URL: - for issue_activity in issue_activities_created: - headers = {"Content-Type": "application/json"} - issue_activity_json = json.dumps( - IssueActivitySerializer(issue_activity).data, - cls=DjangoJSONEncoder, - ) - _ = requests.post( - f"{settings.PROXY_BASE_URL}/hooks/workspaces/{str(issue_activity.workspace_id)}/projects/{str(issue_activity.project_id)}/issues/{str(issue_activity.issue_id)}/issue-activity-hooks/", - json=issue_activity_json, - headers=headers, + try: + if settings.PROXY_BASE_URL: + for issue_activity in issue_activities_created: + headers = {"Content-Type": "application/json"} + issue_activity_json = json.dumps( + IssueActivitySerializer(issue_activity).data, + cls=DjangoJSONEncoder, + ) + _ = requests.post( + f"{settings.PROXY_BASE_URL}/hooks/workspaces/{str(issue_activity.workspace_id)}/projects/{str(issue_activity.project_id)}/issues/{str(issue_activity.issue_id)}/issue-activity-hooks/", + json=issue_activity_json, + headers=headers, + ) + except Exception as e: + capture_exception(e) + + # Create Notifications + bulk_notifications = [] + + issue_subscribers = ( + IssueSubscriber.objects.filter(project=project) + .exclude(subscriber_id=actor_id) + .values_list("subscriber") + ) + + issue = Issue.objects.get(project=project, pk=issue_id) + for subscriber in issue_subscribers: + for issue_activity in issue_activities_created: + bulk_notifications.append( + Notification( + workspace=project.workspace, + sender="in_app:issue_activities", + triggered_by=actor, + receiver_id=subscriber, + entity_identifier=issue_id, + entity_name="issue", + project=project, + title=issue_activity.comment, + data={ + "issue": { + "id": str(issue_id), + "identifier": str(project.identifier), + "sequence_id": issue.sequence_id, + "state_name": issue.state.name, + "state_group": issue.state.group, + }, + "issue_activity": str(issue_activity.id), + }, ) + ) + return except Exception as e: capture_exception(e) diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 47585207c..1c075478d 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -67,4 +67,7 @@ from .page import Page, PageBlock, PageFavorite, PageLabel from .estimate import Estimate, EstimatePoint from .inbox import Inbox, InboxIssue + from .analytic import AnalyticView + +from .notification import Notification \ No newline at end of file diff --git a/apiserver/plane/db/models/notification.py b/apiserver/plane/db/models/notification.py index 312f2933f..10ee1e709 100644 --- a/apiserver/plane/db/models/notification.py +++ b/apiserver/plane/db/models/notification.py @@ -12,12 +12,15 @@ class Notification(BaseModel): project = models.ForeignKey( "db.Project", related_name="notifications", on_delete=models.CASCADE, null=True ) + data = models.JSONField(null=True) entity_identifier = models.UUIDField(null=True) + entity_name = models.CharField(max_length=255) title = models.TextField() message = models.JSONField(null=True) message_html = models.TextField(blank=True, default="

") message_stripped = models.TextField(blank=True, null=True) - sender = models.ForeignKey("db.User", related_name="sent_notifications", on_delete=models.SET_NULL, null=True) + sender = models.CharField(max_length=255) + triggered_by = models.ForeignKey("db.User", related_name="triggered_notifications", on_delete=models.SET_NULL, null=True) receiver = models.ForeignKey("db.User", related_name="received_notifications", on_delete=models.CASCADE) read_at = models.DateTimeField(null=True) snoozed_till = models.DateTimeField(null=True)