Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[back] feat(notifications): u#4007 notify when a new comment has been created #533

Merged
merged 1 commit into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .github/sql-fixtures/fixtures.sql
Binary file not shown.
1 change: 1 addition & 0 deletions .github/workflows/python-api-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:

env:
TAIGA_SECRET_KEY: secret
TAIGA_DEBUG: true
tdelatorre marked this conversation as resolved.
Show resolved Hide resolved

jobs:
test:
Expand Down
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
Loading