From 6ee86c534f34dd9ed11e6eaf1b21cc92feb7aa1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 23 Oct 2023 12:42:45 +0200 Subject: [PATCH] feat(notifications): u#4007 notify when a new comment has been created --- .../taiga/src/taiga/comments/notifications.py | 16 +++++++++ .../__init__.py} | 0 .../src/taiga/comments/serializers/nested.py | 21 ++++++++++++ .../apps/taiga/src/taiga/comments/services.py | 4 +++ .../taiga/src/taiga/stories/comments/api.py | 17 ++++++++-- .../comments/notifications/__init__.py | 33 +++++++++++++++++++ .../stories/comments/notifications/content.py | 19 +++++++++++ .../stories/stories/services/__init__.py | 3 +- 8 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 python/apps/taiga/src/taiga/comments/notifications.py rename python/apps/taiga/src/taiga/comments/{serializers.py => serializers/__init__.py} (100%) create mode 100644 python/apps/taiga/src/taiga/comments/serializers/nested.py create mode 100644 python/apps/taiga/src/taiga/stories/comments/notifications/__init__.py create mode 100644 python/apps/taiga/src/taiga/stories/comments/notifications/content.py diff --git a/python/apps/taiga/src/taiga/comments/notifications.py b/python/apps/taiga/src/taiga/comments/notifications.py new file mode 100644 index 000000000..6ea79ecd6 --- /dev/null +++ b/python/apps/taiga/src/taiga/comments/notifications.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2023-present Kaleidos INC + +from typing import Awaitable, Protocol + +from taiga.comments.models import Comment +from taiga.users.models import User + + +class NotificationOnCreateCallable(Protocol): + def __call__(self, comment: Comment, emitted_by: User) -> Awaitable[None]: + ... diff --git a/python/apps/taiga/src/taiga/comments/serializers.py b/python/apps/taiga/src/taiga/comments/serializers/__init__.py similarity index 100% rename from python/apps/taiga/src/taiga/comments/serializers.py rename to python/apps/taiga/src/taiga/comments/serializers/__init__.py diff --git a/python/apps/taiga/src/taiga/comments/serializers/nested.py b/python/apps/taiga/src/taiga/comments/serializers/nested.py new file mode 100644 index 000000000..0b6f8a066 --- /dev/null +++ b/python/apps/taiga/src/taiga/comments/serializers/nested.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2023-present Kaleidos INC + +from datetime import datetime + +from taiga.base.serializers import UUIDB64, BaseModel +from taiga.users.serializers.nested import UserNestedSerializer + + +class CommentNestedSerializer(BaseModel): + id: UUIDB64 + text: str + created_at: datetime + created_by: UserNestedSerializer | None + + class Config: + orm_mode = True diff --git a/python/apps/taiga/src/taiga/comments/services.py b/python/apps/taiga/src/taiga/comments/services.py index de7b756ab..1581cecda 100644 --- a/python/apps/taiga/src/taiga/comments/services.py +++ b/python/apps/taiga/src/taiga/comments/services.py @@ -14,6 +14,7 @@ from taiga.comments import repositories as comments_repositories from taiga.comments.events import EventOnCreateCallable, EventOnDeleteCallable, EventOnUpdateCallable from taiga.comments.models import Comment +from taiga.comments.notifications import NotificationOnCreateCallable from taiga.comments.repositories import CommentFilters, CommentOrderBy from taiga.stories.stories.models import Story from taiga.users.models import User @@ -28,6 +29,7 @@ async def create_comment( text: str, created_by: User, event_on_create: EventOnCreateCallable | None = None, + notification_on_create: NotificationOnCreateCallable | None = None, ) -> Comment: comment = await comments_repositories.create_comment( content_object=content_object, @@ -37,6 +39,8 @@ async def create_comment( if event_on_create: await event_on_create(comment=comment) + if notification_on_create: + await notification_on_create(comment=comment, emitted_by=created_by) return comment diff --git a/python/apps/taiga/src/taiga/stories/comments/api.py b/python/apps/taiga/src/taiga/stories/comments/api.py index 88333b2fa..86482b24e 100644 --- a/python/apps/taiga/src/taiga/stories/comments/api.py +++ b/python/apps/taiga/src/taiga/stories/comments/api.py @@ -23,7 +23,7 @@ from taiga.exceptions.api.errors import ERROR_403, ERROR_404, ERROR_422 from taiga.permissions import HasPerm, IsNotDeleted, IsProjectAdmin, IsRelatedToTheUser from taiga.routers import routes -from taiga.stories.comments import events +from taiga.stories.comments import events, notifications from taiga.stories.stories.api import get_story_or_404 from taiga.stories.stories.models import Story @@ -60,9 +60,20 @@ async def create_story_comments( story = await get_story_or_404(project_id=project_id, ref=ref) await check_permissions(permissions=CREATE_STORY_COMMENT, user=request.user, obj=story) - event_on_create = partial(events.emit_event_when_story_comment_is_created, project=story.project) + event_on_create = partial( + events.emit_event_when_story_comment_is_created, + project=story.project, + ) + notification_on_create = partial( + notifications.notify_when_story_comment_is_created, + story=story, + ) return await comments_services.create_comment( - text=form.text, content_object=story, created_by=request.user, event_on_create=event_on_create + text=form.text, + content_object=story, + created_by=request.user, + event_on_create=event_on_create, + notification_on_create=notification_on_create, ) diff --git a/python/apps/taiga/src/taiga/stories/comments/notifications/__init__.py b/python/apps/taiga/src/taiga/stories/comments/notifications/__init__.py new file mode 100644 index 000000000..f7b42af84 --- /dev/null +++ b/python/apps/taiga/src/taiga/stories/comments/notifications/__init__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2023-present Kaleidos INC + +from taiga.comments.models import Comment +from taiga.notifications import services as notifications_services +from taiga.stories.comments.notifications.content import StoryCommentCreateNotificationContent +from taiga.stories.stories.models import Story +from taiga.users.models import User + +STORY_COMMENT_CREATE = "story_comment.create" + + +async def notify_when_story_comment_is_created(story: Story, comment: Comment, emitted_by: User) -> None: + notified_users = {u async for u in story.assignees.all()} + if story.created_by: + notified_users.add(story.created_by) + notified_users.discard(emitted_by) + + await notifications_services.notify_users( + type=STORY_COMMENT_CREATE, + emitted_by=emitted_by, + notified_users=notified_users, + content=StoryCommentCreateNotificationContent( + project=story.project, + story=story, + commented_by=emitted_by, + comment=comment, + ), + ) diff --git a/python/apps/taiga/src/taiga/stories/comments/notifications/content.py b/python/apps/taiga/src/taiga/stories/comments/notifications/content.py new file mode 100644 index 000000000..a07b11740 --- /dev/null +++ b/python/apps/taiga/src/taiga/stories/comments/notifications/content.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2023-present Kaleidos INC + +from taiga.base.serializers import BaseModel +from taiga.comments.serializers.nested import CommentNestedSerializer +from taiga.projects.projects.serializers.nested import ProjectLinkNestedSerializer +from taiga.stories.stories.serializers.nested import StoryNestedSerializer +from taiga.users.serializers.nested import UserNestedSerializer + + +class StoryCommentCreateNotificationContent(BaseModel): + project: ProjectLinkNestedSerializer + story: StoryNestedSerializer + commented_by: UserNestedSerializer + comment: CommentNestedSerializer diff --git a/python/apps/taiga/src/taiga/stories/stories/services/__init__.py b/python/apps/taiga/src/taiga/stories/stories/services/__init__.py index 4aabcd5ac..277f5592d 100644 --- a/python/apps/taiga/src/taiga/stories/stories/services/__init__.py +++ b/python/apps/taiga/src/taiga/stories/stories/services/__init__.py @@ -110,6 +110,7 @@ async def get_story(project_id: UUID, ref: int) -> Story | None: return await stories_repositories.get_story( filters={"ref": ref, "project_id": project_id}, select_related=["project", "workspace", "workflow", "created_by"], + prefetch_related=["assignees"], ) @@ -362,4 +363,4 @@ async def delete_story(story: Story, deleted_by: AnyUser) -> bool: ) return True - return False \ No newline at end of file + return False