From e77a0487e726e146d63bcc2aae21d2c2a3333874 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Mon, 30 Jun 2025 17:56:22 +0530 Subject: [PATCH] [MOBIL-1023]: chore: Add new fields to Inbox (Notification) query (#3539) * dev: updated inbox query * dev: handled the notification * dev: handled the epic notifications * chore: removed is_inbpx_issue field --- .../plane/app/views/notification/base.py | 2 +- apiserver/plane/ee/views/app/inbox/base.py | 2 +- .../plane/graphql/queries/notification.py | 64 ++++++++++++------- apiserver/plane/graphql/types/notification.py | 58 +++++++++++++++-- 4 files changed, 94 insertions(+), 32 deletions(-) diff --git a/apiserver/plane/app/views/notification/base.py b/apiserver/plane/app/views/notification/base.py index d4b98ba9eb..2fbfb905a8 100644 --- a/apiserver/plane/app/views/notification/base.py +++ b/apiserver/plane/app/views/notification/base.py @@ -37,7 +37,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator): workspace__slug=self.kwargs.get("slug"), receiver_id=self.request.user.id, ) - .select_related("workspace", "project," "triggered_by", "receiver") + .select_related("workspace", "project", "triggered_by", "receiver") ) @allow_permission( diff --git a/apiserver/plane/ee/views/app/inbox/base.py b/apiserver/plane/ee/views/app/inbox/base.py index 8f4acf2a22..23d9daa520 100644 --- a/apiserver/plane/ee/views/app/inbox/base.py +++ b/apiserver/plane/ee/views/app/inbox/base.py @@ -27,7 +27,7 @@ class InboxViewSet(BaseViewSet, BasePaginator): workspace__slug=self.kwargs.get("slug"), receiver_id=self.request.user.id, ) - .select_related("workspace", "project," "triggered_by", "receiver") + .select_related("workspace", "project", "triggered_by", "receiver") ) @check_feature_flag(FeatureFlag.INBOX_STACKING) diff --git a/apiserver/plane/graphql/queries/notification.py b/apiserver/plane/graphql/queries/notification.py index f71fc96b42..1bb9f46121 100644 --- a/apiserver/plane/graphql/queries/notification.py +++ b/apiserver/plane/graphql/queries/notification.py @@ -41,24 +41,23 @@ class NotificationQuery: type: Optional[JSON] = "all", snoozed: Optional[bool] = None, archived: Optional[bool] = None, - read: Optional[str] = None, mentioned: Optional[bool] = False, + read: Optional[str] = None, cursor: Optional[str] = None, ) -> PaginatorResponse[NotificationType]: user = info.context.user user_id = str(user.id) - type_list = type.split(",") - q_filters = Q() - - filters = Q(workspace__slug=slug, receiver_id=user_id) - - # Base QuerySet + # Teamspace Filter project_teamspace_filter = await project_member_filter_via_teamspaces_async( user_id=user_id, workspace_slug=slug ) - queryset = ( - Notification.objects.filter(filters) + + # Inbox Filters + notification_queryset = ( + Notification.objects.filter(workspace__slug=slug) + .filter(receiver_id=user_id) + .filter(entity_name__in=["issue", "epic"]) .filter(project_teamspace_filter.query) .distinct() .select_related("workspace", "project", "triggered_by", "receiver") @@ -66,38 +65,53 @@ class NotificationQuery: ) now = timezone.now() + q_filters = Q() # Apply snoozed filter if snoozed is not None: if snoozed: - queryset = queryset.filter( + notification_queryset = notification_queryset.filter( Q(snoozed_till__lt=now) | Q(snoozed_till__isnull=False) ) else: - queryset = queryset.filter( + notification_queryset = notification_queryset.filter( Q(snoozed_till__gte=now) | Q(snoozed_till__isnull=True) ) # Apply archived filter if archived is not None: if archived: - queryset = queryset.filter(archived_at__isnull=False) + notification_queryset = notification_queryset.filter( + archived_at__isnull=False + ) else: - queryset = queryset.filter(archived_at__isnull=True) + notification_queryset = notification_queryset.filter( + archived_at__isnull=True + ) # Apply read filter if read is not None: if read == "true": - queryset = queryset.filter(read_at__isnull=False) + notification_queryset = notification_queryset.filter( + read_at__isnull=False + ) elif read == "false": - queryset = queryset.filter(read_at__isnull=True) + notification_queryset = notification_queryset.filter( + read_at__isnull=True + ) + # Apply mentioned filter if mentioned: - queryset = queryset.filter(sender__icontains="mentioned") + notification_queryset = notification_queryset.filter( + sender__icontains="mentioned" + ) else: - queryset = queryset.exclude(sender__icontains="mentioned") + notification_queryset = notification_queryset.exclude( + sender__icontains="mentioned" + ) # Subscribed issues + type_list = type.split(",") if "subscribed" in type_list: issue_ids = await sync_to_async(list)( IssueSubscriber.objects.filter( @@ -105,7 +119,9 @@ class NotificationQuery: ) .annotate( created=Exists( - Issue.objects.filter(created_by=user, pk=OuterRef("issue_id")) + Issue.objects.filter( + created_by=user, pk=OuterRef("issue_id") + ).filter(Q(type__isnull=True) | Q(type__is_epic=False)) ) ) .annotate( @@ -141,16 +157,16 @@ class NotificationQuery: )() if has_permission: - queryset = queryset.none() + notification_queryset = notification_queryset.none() else: issue_ids = await sync_to_async(list)( - Issue.objects.filter( - workspace__slug=slug, created_by=user - ).values_list("pk", flat=True) + Issue.objects.filter(workspace__slug=slug, created_by=user) + .filter(Q(type__isnull=True) | Q(type__is_epic=False)) + .values_list("pk", flat=True) ) q_filters = Q(entity_identifier__in=issue_ids) - queryset = queryset.filter(q_filters) - notifications = await sync_to_async(list)(queryset) + notification_queryset = notification_queryset.filter(q_filters) + notifications = await sync_to_async(list)(notification_queryset) return paginate(results_object=notifications, cursor=cursor) diff --git a/apiserver/plane/graphql/types/notification.py b/apiserver/plane/graphql/types/notification.py index f129927e62..dccfc751fc 100644 --- a/apiserver/plane/graphql/types/notification.py +++ b/apiserver/plane/graphql/types/notification.py @@ -1,19 +1,23 @@ # Python imports -from typing import Optional from datetime import datetime -from asgiref.sync import sync_to_async +from typing import Optional # Strawberry imports import strawberry import strawberry_django +from asgiref.sync import sync_to_async + +# Django imports +from django.db.models import Q from strawberry.scalars import JSON +from plane.db.models import Issue, Notification +from plane.graphql.types.project import ProjectType +from plane.graphql.types.users import UserType +from plane.graphql.utils.issue_activity import issue_activity_comment_string + # Module imports from plane.graphql.utils.timezone import user_timezone_converter -from plane.db.models import Notification -from plane.graphql.types.users import UserType -from plane.graphql.types.project import ProjectType -from plane.graphql.utils.issue_activity import issue_activity_comment_string @strawberry_django.type(Notification) @@ -97,3 +101,45 @@ class NotificationType: return self.data return await process_data() + + @strawberry.field + async def is_intake_issue(self) -> bool: + workspace_slug = await sync_to_async(lambda: self.workspace.slug)() + work_item_id = self.entity_identifier + + if not workspace_slug or not work_item_id: + return False + + is_intake_issue = await sync_to_async(list)( + Issue.objects.filter( + workspace__slug=workspace_slug, + pk=work_item_id, + issue_intake__status__in=[0, 2, -2], + ) + ) + + return bool(is_intake_issue) + + @strawberry.field + async def is_mentioned_notification(self) -> bool: + is_mentioned_notification = ( + True if "mentioned" in self.sender.lower() else False + ) + + return is_mentioned_notification + + @strawberry.field + async def is_epic(self) -> bool: + workspace_slug = await sync_to_async(lambda: self.workspace.slug)() + work_item_id = self.entity_identifier + + if not work_item_id or not workspace_slug: + return False + + is_epic_work_item = await sync_to_async(list)( + Issue.objects.filter( + pk=work_item_id, workspace__slug=workspace_slug + ).filter(Q(type__isnull=False) & Q(type__is_epic=True)) + ) + + return bool(is_epic_work_item)