Skip to content

Commit

Permalink
feat(notifications): u#4007 notify when a new comment has been created
Browse files Browse the repository at this point in the history
  • Loading branch information
bameda committed Oct 23, 2023
1 parent 5198f51 commit d2ecf68
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 5 deletions.
16 changes: 16 additions & 0 deletions python/apps/taiga/src/taiga/comments/notifications.py
Original file line number Diff line number Diff line change
@@ -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]:
...
21 changes: 21 additions & 0 deletions python/apps/taiga/src/taiga/comments/serializers/nested.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions python/apps/taiga/src/taiga/comments/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- 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

# Generated by Django 4.2.3 on 2023-10-23 11:16

import django.contrib.postgres.fields.jsonb
import taiga.base.utils.json
from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("notifications", "0001_initial"),
]

operations = [
migrations.AlterField(
model_name="notification",
name="content",
field=django.contrib.postgres.fields.jsonb.JSONField(
default=dict,
encoder=taiga.base.utils.json.JSONEncoder,
verbose_name="content",
),
),
]
2 changes: 2 additions & 0 deletions python/apps/taiga/src/taiga/notifications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from taiga.base.db import models
from taiga.base.db.mixins import CreatedMetaInfoMixin
from taiga.base.utils import json

#######################################################################
# Base Notification
Expand Down Expand Up @@ -37,6 +38,7 @@ class Notification(models.BaseModel, CreatedMetaInfoMixin):
null=False,
blank=False,
default=dict,
encoder=json.JSONEncoder,
verbose_name="content",
)

Expand Down
17 changes: 14 additions & 3 deletions python/apps/taiga/src/taiga/stories/comments/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
)


Expand Down
Original file line number Diff line number Diff line change
@@ -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,
),
)
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
)


Expand Down Expand Up @@ -362,4 +363,4 @@ async def delete_story(story: Story, deleted_by: AnyUser) -> bool:
)
return True

return False
return False
29 changes: 28 additions & 1 deletion python/apps/taiga/tests/unit/taiga/comments/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def test_create_comment():
)


async def test_create_comment_and_emit_event_on_create():
async def test_create_comment_and_emit_event_on_creation():
project = f.build_project()
story = f.build_story(project=project)
fake_event_on_create = AsyncMock()
Expand All @@ -68,6 +68,33 @@ async def test_create_comment_and_emit_event_on_create():
fake_event_on_create.assert_awaited_once_with(comment=comment)


async def test_create_comment_and_notify_on_creation():
project = f.build_project()
story = f.build_story(project=project)
fake_notification_on_create = AsyncMock()
comment = f.build_comment()

with (
patch("taiga.comments.services.comments_repositories", autospec=True) as fake_comments_repositories,
patch("taiga.comments.models.Comment.project", new_callable=PropertyMock, return_value=project),
):
fake_comments_repositories.create_comment.return_value = comment

await services.create_comment(
content_object=story,
text=comment.text,
created_by=comment.created_by,
notification_on_create=fake_notification_on_create,
)

fake_comments_repositories.create_comment.assert_awaited_once_with(
content_object=story,
text=comment.text,
created_by=comment.created_by,
)
fake_notification_on_create.assert_awaited_once_with(comment=comment, emitted_by=comment.created_by)


#####################################################
# list_comments
#####################################################
Expand Down

0 comments on commit d2ecf68

Please sign in to comment.