Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.

Commit 2dde1cc

Browse files
authored
feat(case): add case feedback daily email (#5395)
1 parent 183eee4 commit 2dde1cc

File tree

5 files changed

+126
-14
lines changed

5 files changed

+126
-14
lines changed

src/dispatch/feedback/incident/messaging.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import logging
22
from typing import List
33

4-
from dispatch.database.core import SessionLocal
4+
from sqlalchemy.orm import Session
5+
56
from dispatch.messaging.strings import (
67
INCIDENT_FEEDBACK_DAILY_REPORT,
8+
CASE_FEEDBACK_DAILY_REPORT,
79
MessageType,
810
)
911
from dispatch.plugin import service as plugin_service
@@ -15,7 +17,7 @@
1517

1618

1719
def send_incident_feedback_daily_report(
18-
commander_email: str, feedback: List[Feedback], project_id: int, db_session: SessionLocal
20+
commander_email: str, feedback: List[Feedback], project_id: int, db_session: Session
1921
):
2022
"""Sends an incident feedback daily report to all incident commanders who received feedback."""
2123
plugin = plugin_service.get_active_instance(
@@ -62,3 +64,53 @@ def send_incident_feedback_daily_report(
6264
log.error(f"Error in sending {notification_text} email to {commander_email}: {e}")
6365

6466
log.debug(f"Incident feedback daily report sent to {commander_email}.")
67+
68+
69+
def send_case_feedback_daily_report(
70+
assignee_email: str, feedback: List[Feedback], project_id: int, db_session: Session
71+
):
72+
"""Sends an case feedback daily report to all case assignees who received feedback."""
73+
plugin = plugin_service.get_active_instance(
74+
db_session=db_session, project_id=project_id, plugin_type="email"
75+
)
76+
77+
if not plugin:
78+
log.warning("Case feedback daily report not sent. Email plugin is not enabled.")
79+
return
80+
81+
items = []
82+
for piece in feedback:
83+
participant = piece.participant.individual.name if piece.participant else "Anonymous"
84+
items.append(
85+
{
86+
"name": piece.case.name,
87+
"title": piece.case.title,
88+
"rating": piece.rating,
89+
"feedback": piece.feedback,
90+
"participant": participant,
91+
"created_at": piece.created_at,
92+
}
93+
)
94+
95+
name = subject = notification_text = "Case Feedback Daily Report"
96+
assignee_fullname = feedback[0].case.assignee.individual.name
97+
assignee_weblink = feedback[0].case.assignee.individual.weblink
98+
99+
# Can raise exception "tenacity.RetryError: RetryError". (Email may still go through).
100+
try:
101+
plugin.instance.send(
102+
assignee_email,
103+
notification_text,
104+
CASE_FEEDBACK_DAILY_REPORT,
105+
MessageType.case_feedback_daily_report,
106+
name=name,
107+
subject=subject,
108+
cc=plugin.project.owner_email,
109+
items=items,
110+
contact_fullname=assignee_fullname,
111+
contact_weblink=assignee_weblink,
112+
)
113+
except Exception as e:
114+
log.error(f"Error in sending {notification_text} email to {assignee_email}: {e}")
115+
116+
log.debug(f"Case feedback daily report sent to {assignee_email}.")

src/dispatch/feedback/incident/scheduled.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
from schedule import every
33
import logging
44

5-
from dispatch.database.core import SessionLocal
5+
from sqlalchemy.orm import Session
6+
67
from dispatch.decorators import scheduled_project_task, timer
78
from dispatch.project.models import Project
89
from dispatch.scheduler import scheduler
910

10-
from .messaging import send_incident_feedback_daily_report
11-
from .service import get_all_last_x_hours_by_project_id
11+
from .messaging import send_incident_feedback_daily_report, send_case_feedback_daily_report
12+
from .service import (
13+
get_all_incident_last_x_hours_by_project_id,
14+
get_all_case_last_x_hours_by_project_id,
15+
)
1216

1317
log = logging.getLogger(__name__)
1418

@@ -17,21 +21,42 @@ def group_feedback_by_commander(feedback):
1721
"""Groups feedback by commander."""
1822
grouped = defaultdict(lambda: [])
1923
for piece in feedback:
20-
grouped[piece.incident.commander.individual.email].append(piece)
24+
if piece.incident and piece.incident.commander:
25+
grouped[piece.incident.commander.individual.email].append(piece)
26+
return grouped
27+
28+
29+
def group_feedback_by_assignee(feedback):
30+
"""Groups feedback by assignee."""
31+
grouped = defaultdict(lambda: [])
32+
for piece in feedback:
33+
if piece.case and piece.case.assignee:
34+
grouped[piece.case.assignee.individual.email].append(piece)
2135
return grouped
2236

2337

2438
@scheduler.add(every(1).day.at("18:00"), name="feedback-report-daily")
2539
@timer
2640
@scheduled_project_task
27-
def feedback_report_daily(db_session: SessionLocal, project: Project):
41+
def feedback_report_daily(db_session: Session, project: Project):
2842
"""
29-
Fetches all incident feedback provided in the last 24 hours
30-
and sends a daily report to the commanders who handled the incidents.
43+
Fetches all incident and case feedback provided in the last 24 hours
44+
and sends a daily report to the commanders and assignees who handled the incidents/cases.
3145
"""
32-
feedback = get_all_last_x_hours_by_project_id(db_session=db_session, project_id=project.id)
46+
incident_feedback = get_all_incident_last_x_hours_by_project_id(
47+
db_session=db_session, project_id=project.id
48+
)
3349

34-
if feedback:
35-
grouped_feedback = group_feedback_by_commander(feedback)
36-
for commander_email, feedback in grouped_feedback.items():
50+
if incident_feedback:
51+
grouped_incident_feedback = group_feedback_by_commander(incident_feedback)
52+
for commander_email, feedback in grouped_incident_feedback.items():
3753
send_incident_feedback_daily_report(commander_email, feedback, project.id, db_session)
54+
55+
case_feedback = get_all_case_last_x_hours_by_project_id(
56+
db_session=db_session, project_id=project.id
57+
)
58+
59+
if case_feedback:
60+
grouped_case_feedback = group_feedback_by_assignee(case_feedback)
61+
for assignee_email, feedback in grouped_case_feedback.items():
62+
send_case_feedback_daily_report(assignee_email, feedback, project.id, db_session)

src/dispatch/feedback/incident/service.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from dispatch.incident import service as incident_service
55
from dispatch.case import service as case_service
66
from dispatch.incident.models import Incident
7+
from dispatch.case.models import Case
78
from dispatch.project.models import Project
89

910
from .models import Feedback, FeedbackCreate, FeedbackUpdate
@@ -19,7 +20,7 @@ def get_all(*, db_session):
1920
return db_session.query(Feedback)
2021

2122

22-
def get_all_last_x_hours_by_project_id(
23+
def get_all_incident_last_x_hours_by_project_id(
2324
*, db_session, hours: int = 24, project_id: int
2425
) -> List[Optional[Feedback]]:
2526
"""Returns all feedback provided in the last x hours by project id. Defaults to 24 hours."""
@@ -33,6 +34,20 @@ def get_all_last_x_hours_by_project_id(
3334
)
3435

3536

37+
def get_all_case_last_x_hours_by_project_id(
38+
*, db_session, hours: int = 24, project_id: int
39+
) -> List[Optional[Feedback]]:
40+
"""Returns all feedback provided in the last x hours by project id. Defaults to 24 hours."""
41+
return (
42+
db_session.query(Feedback)
43+
.join(Case)
44+
.join(Project)
45+
.filter(Project.id == project_id)
46+
.filter(Feedback.created_at >= datetime.utcnow() - timedelta(hours=hours))
47+
.all()
48+
)
49+
50+
3651
def create(*, db_session, feedback_in: FeedbackCreate) -> Feedback:
3752
"""Creates a new piece of feedback."""
3853
if feedback_in.incident:

src/dispatch/messaging/email/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
INCIDENT_DAILY_REPORT_DESCRIPTION,
1313
INCIDENT_FEEDBACK_DAILY_REPORT_DESCRIPTION,
1414
INCIDENT_TASK_REMINDER_DESCRIPTION,
15+
CASE_FEEDBACK_DAILY_REPORT_DESCRIPTION,
1516
MessageType,
1617
render_message_template,
1718
)
@@ -42,6 +43,10 @@ def get_template(message_type: MessageType, project_id: int):
4243
"notification_list.mjml",
4344
INCIDENT_FEEDBACK_DAILY_REPORT_DESCRIPTION,
4445
),
46+
MessageType.case_feedback_daily_report: (
47+
"notification_list.mjml",
48+
CASE_FEEDBACK_DAILY_REPORT_DESCRIPTION,
49+
),
4550
MessageType.incident_daily_report: (
4651
"notification_list.mjml",
4752
INCIDENT_DAILY_REPORT_DESCRIPTION,

src/dispatch/messaging/strings.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class MessageType(DispatchEnum):
4343
service_feedback = "service-feedback"
4444
task_add_to_incident = "task-add-to-incident"
4545
case_rating_feedback = "case-rating-feedback"
46+
case_feedback_daily_report = "case-feedback-daily-report"
4647

4748

4849
INCIDENT_STATUS_DESCRIPTIONS = {
@@ -80,6 +81,11 @@ class MessageType(DispatchEnum):
8081
"\n", " "
8182
).strip()
8283

84+
CASE_FEEDBACK_DAILY_REPORT_DESCRIPTION = """
85+
This is a daily report of feedback about cases handled by you.""".replace(
86+
"\n", " "
87+
).strip()
88+
8389
INCIDENT_WEEKLY_REPORT_TITLE = """
8490
Incidents Weekly Report""".replace(
8591
"\n", " "
@@ -999,6 +1005,15 @@ class MessageType(DispatchEnum):
9991005
{"title": "Created At", "text": "", "datetime": "{{ created_at}}"},
10001006
]
10011007

1008+
CASE_FEEDBACK_DAILY_REPORT = [
1009+
{"title": "Case", "text": "{{ name }}"},
1010+
{"title": "Case Title", "text": "{{ title }}"},
1011+
{"title": "Rating", "text": "{{ rating }}"},
1012+
{"title": "Feedback", "text": "{{ feedback }}"},
1013+
{"title": "Participant", "text": "{{ participant }}"},
1014+
{"title": "Created At", "text": "", "datetime": "{{ created_at}}"},
1015+
]
1016+
10021017
INCIDENT_WEEKLY_REPORT_HEADER = {
10031018
"type": "header",
10041019
"text": INCIDENT_WEEKLY_REPORT_TITLE,

0 commit comments

Comments
 (0)