From 707c090290889950eb3fc8515187447d57be0849 Mon Sep 17 00:00:00 2001 From: Lukasz Sojka Date: Fri, 20 Dec 2024 18:33:27 +0100 Subject: [PATCH] improvement(highlights): send notification to action item assignee When assigning action item in Highlights Widget, assignee will get Argus Notification with link to the view and action details. closes: https://github.com/scylladb/argus/issues/500 --- .../controller/views_widgets/highlights.py | 6 ++-- argus/backend/models/web.py | 2 ++ argus/backend/service/notification_manager.py | 4 +++ .../service/views_widgets/highlights.py | 30 +++++++++++++++++-- .../tests/view_widgets/test_highlights_api.py | 6 ++-- .../view_action_item_assigned.html.j2 | 3 ++ .../view_action_item_assigned_email.html.j2 | 4 +++ 7 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 templates/notifications/view_action_item_assigned.html.j2 create mode 100644 templates/notifications/view_action_item_assigned_email.html.j2 diff --git a/argus/backend/controller/views_widgets/highlights.py b/argus/backend/controller/views_widgets/highlights.py index 91b64897..e4f782ef 100644 --- a/argus/backend/controller/views_widgets/highlights.py +++ b/argus/backend/controller/views_widgets/highlights.py @@ -1,5 +1,4 @@ -from dataclasses import dataclass, asdict -from datetime import datetime, UTC +from dataclasses import asdict from uuid import UUID from flask import Blueprint, request, g @@ -80,6 +79,9 @@ def set_assignee(): payload = HighlightSetAssignee(**get_payload(request)) service = HighlightsService() updated_action_item = service.set_assignee(payload) + if payload.assignee_id: + service.send_action_notification(sender_id=g.user.id, username=g.user.username, view_id=payload.view_id, + assignee_id=payload.assignee_id, action=updated_action_item.content) return {"status": "ok", "response": asdict(updated_action_item)} diff --git a/argus/backend/models/web.py b/argus/backend/models/web.py index e003df11..d54df95a 100644 --- a/argus/backend/models/web.py +++ b/argus/backend/models/web.py @@ -236,12 +236,14 @@ class ArgusNotificationTypes(str, Enum): StatusChange = "TYPE_STATUS_CHANGE" AssigneeChange = "TYPE_ASSIGNEE_CHANGE" ScheduleChange = "TYPE_SCHEDULE_CHANGE" + ViewActionItemAssignee = "TYPE_VIEW_ACTION_ITEM_ASSIGNEE" class ArgusNotificationSourceTypes(str, Enum): TestRun = "TEST_RUN" Schedule = "SCHEDULE" Comment = "COMMENT" + ViewActionItem = "VIEW_ACTION_ITEM" class ArgusNotificationState(IntEnum): diff --git a/argus/backend/service/notification_manager.py b/argus/backend/service/notification_manager.py index 49fff6e9..0fae85fa 100644 --- a/argus/backend/service/notification_manager.py +++ b/argus/backend/service/notification_manager.py @@ -57,6 +57,7 @@ class NotificationSenderBase: ArgusNotificationTypes.StatusChange: "A run you are assigned to changed status", ArgusNotificationTypes.AssigneeChange: "You were assigned to a run", ArgusNotificationTypes.ScheduleChange: "You were assigned to a schedule", + ArgusNotificationTypes.ViewActionItemAssignee: "You were assigned to a view action item", } CONTENT_TEMPLATES = {} @@ -95,6 +96,8 @@ class ArgusDBNotificationSaver(NotificationSenderBase): CONTENT_TEMPLATES = { ArgusNotificationTypes.Mention: lambda p: render_template("notifications/mention.html.j2", **p if p else {}), ArgusNotificationTypes.AssigneeChange: lambda p: render_template("notifications/assigned.html.j2", **p if p else {}), + ArgusNotificationTypes.ViewActionItemAssignee: lambda p: render_template( + "notifications/view_action_item_assigned.html.j2", **p if p else {}), } def send_notification(self, receiver: UUID, sender: UUID, notification_type: ArgusNotificationTypes, source_type: ArgusNotificationSourceTypes, @@ -120,6 +123,7 @@ class EmailNotificationServiceSender(NotificationSenderBase): ArgusNotificationTypes.Mention: lambda p: render_template( "notifications/email_mention.html.j2", **p if p else {}), ArgusNotificationTypes.AssigneeChange: lambda p: render_template("notifications/assigned_email.html.j2", **p if p else {}), + ArgusNotificationTypes.ViewActionItemAssignee: lambda p: render_template("notifications/view_action_item_assigned_email.html.j2", **p if p else {}), } def __init__(self): diff --git a/argus/backend/service/views_widgets/highlights.py b/argus/backend/service/views_widgets/highlights.py index abf64d58..68bf097a 100644 --- a/argus/backend/service/views_widgets/highlights.py +++ b/argus/backend/service/views_widgets/highlights.py @@ -6,6 +6,8 @@ from argus.backend.db import ScyllaCluster from argus.backend.models.view_widgets import WidgetHighlights, WidgetComment +from argus.backend.models.web import ArgusNotificationTypes, ArgusNotificationSourceTypes, ArgusUserView +from argus.backend.service.notification_manager import NotificationManagerService @dataclass @@ -71,6 +73,7 @@ class CommentUpdate: created_at: float content: str + @dataclass class CommentDelete: view_id: UUID @@ -78,6 +81,7 @@ class CommentDelete: highlight_created_at: float created_at: float + @dataclass class Comment: view_id: UUID @@ -129,8 +133,11 @@ class HighlightSetAssignee: view_id: UUID index: int created_at: float - assignee_id: str | None = None + assignee_id: UUID | None = None + def __post_init__(self): + if self.assignee_id and not isinstance(self.assignee_id, UUID): + self.assignee_id = UUID(self.assignee_id) @dataclass class HighlightSetCompleted: @@ -174,7 +181,7 @@ def archive_highlight(self, payload: HighlightArchive): entry.archived_at = datetime.now(UTC) entry.save() - def unarchive_highlight(self, payload: HighlightArchive): + def unarchive_highlight(self, payload: HighlightArchive): entry = WidgetHighlights.objects( view_id=payload.view_id, index=payload.index, @@ -212,7 +219,7 @@ def set_assignee(self, payload: HighlightSetAssignee) -> ActionItem: if payload.assignee_id is None: entry.assignee_id = None else: - entry.assignee_id = UUID(payload.assignee_id) + entry.assignee_id = payload.assignee_id entry.save() return ActionItem.from_db_model(entry) @@ -295,3 +302,20 @@ def get_comments(self, view_id: UUID, index: int, highlight_created_at: float) - highlight_created_at = datetime.fromtimestamp(highlight_created_at, tz=UTC) comments = WidgetComment.objects(view_id=view_id, index=index, highlight_at=highlight_created_at) return [Comment.from_db_model(c) for c in comments] + + def send_action_notification(self, sender_id: UUID, username: str, view_id: UUID, assignee_id: UUID, action: str): + view = ArgusUserView.get(id=view_id) + NotificationManagerService().send_notification( + receiver=assignee_id, + sender=sender_id, + notification_type=ArgusNotificationTypes.ViewActionItemAssignee, + source_type=ArgusNotificationSourceTypes.ViewActionItem, + source_id=view_id, + source_message="", + content_params={ + "username": username, + "view_name": view.name, + "display_name": view.display_name, + "action": action, + } + ) diff --git a/argus/backend/tests/view_widgets/test_highlights_api.py b/argus/backend/tests/view_widgets/test_highlights_api.py index 713ac4bd..2228d066 100644 --- a/argus/backend/tests/view_widgets/test_highlights_api.py +++ b/argus/backend/tests/view_widgets/test_highlights_api.py @@ -1,5 +1,6 @@ import json from datetime import datetime, UTC +from unittest.mock import patch from uuid import uuid4, UUID from flask import g @@ -303,8 +304,8 @@ def test_set_completed_should_not_work_for_highlight(flask_client): assert response.json["status"] == "error" assert response.json["response"]["exception"] == "NotFound" - -def test_set_assignee_should_set_assignee_for_action_item(flask_client): +@patch("argus.backend.controller.views_widgets.highlights.HighlightsService.send_action_notification") +def test_set_assignee_should_set_assignee_for_action_item(notification, flask_client): view_id = str(uuid4()) created_at = datetime.now(UTC) action_item_entry = WidgetHighlights( @@ -333,6 +334,7 @@ def test_set_assignee_should_set_assignee_for_action_item(flask_client): assert response.status_code == 200 assert response.json["status"] == "ok" assert response.json["response"]["assignee_id"] == new_assignee_id + assert notification.call_count == 1 updated_entry = WidgetHighlights.objects(view_id=UUID(view_id), index=0, created_at=created_at).first() assert str(updated_entry.assignee_id) == new_assignee_id diff --git a/templates/notifications/view_action_item_assigned.html.j2 b/templates/notifications/view_action_item_assigned.html.j2 new file mode 100644 index 00000000..526ade97 --- /dev/null +++ b/templates/notifications/view_action_item_assigned.html.j2 @@ -0,0 +1,3 @@ +You were assigned by @{{ username }} to View Action {{ display_name }}. +

Action: {{ action }}

diff --git a/templates/notifications/view_action_item_assigned_email.html.j2 b/templates/notifications/view_action_item_assigned_email.html.j2 new file mode 100644 index 00000000..5d338a91 --- /dev/null +++ b/templates/notifications/view_action_item_assigned_email.html.j2 @@ -0,0 +1,4 @@ +
You were assigned by @{{ username }} to View Action{{ display_name }}. +

Action: {{ action }}

+