Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.
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
56 changes: 54 additions & 2 deletions src/dispatch/feedback/incident/messaging.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import logging
from typing import List

from dispatch.database.core import SessionLocal
from sqlalchemy.orm import Session

from dispatch.messaging.strings import (
INCIDENT_FEEDBACK_DAILY_REPORT,
CASE_FEEDBACK_DAILY_REPORT,
MessageType,
)
from dispatch.plugin import service as plugin_service
Expand All @@ -15,7 +17,7 @@


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

log.debug(f"Incident feedback daily report sent to {commander_email}.")


def send_case_feedback_daily_report(
assignee_email: str, feedback: List[Feedback], project_id: int, db_session: Session
):
"""Sends an case feedback daily report to all case assignees who received feedback."""
plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=project_id, plugin_type="email"
)

if not plugin:
log.warning("Case feedback daily report not sent. Email plugin is not enabled.")
return

items = []
for piece in feedback:
participant = piece.participant.individual.name if piece.participant else "Anonymous"
items.append(
{
"name": piece.case.name,
"title": piece.case.title,
"rating": piece.rating,
"feedback": piece.feedback,
"participant": participant,
"created_at": piece.created_at,
}
)

name = subject = notification_text = "Case Feedback Daily Report"
assignee_fullname = feedback[0].case.assignee.individual.name
assignee_weblink = feedback[0].case.assignee.individual.weblink

# Can raise exception "tenacity.RetryError: RetryError". (Email may still go through).
try:
plugin.instance.send(
assignee_email,
notification_text,
CASE_FEEDBACK_DAILY_REPORT,
MessageType.case_feedback_daily_report,
name=name,
subject=subject,
cc=plugin.project.owner_email,
items=items,
contact_fullname=assignee_fullname,
contact_weblink=assignee_weblink,
)
except Exception as e:
log.error(f"Error in sending {notification_text} email to {assignee_email}: {e}")

log.debug(f"Case feedback daily report sent to {assignee_email}.")
47 changes: 36 additions & 11 deletions src/dispatch/feedback/incident/scheduled.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
from schedule import every
import logging

from dispatch.database.core import SessionLocal
from sqlalchemy.orm import Session

from dispatch.decorators import scheduled_project_task, timer
from dispatch.project.models import Project
from dispatch.scheduler import scheduler

from .messaging import send_incident_feedback_daily_report
from .service import get_all_last_x_hours_by_project_id
from .messaging import send_incident_feedback_daily_report, send_case_feedback_daily_report
from .service import (
get_all_incident_last_x_hours_by_project_id,
get_all_case_last_x_hours_by_project_id,
)

log = logging.getLogger(__name__)

Expand All @@ -17,21 +21,42 @@ def group_feedback_by_commander(feedback):
"""Groups feedback by commander."""
grouped = defaultdict(lambda: [])
for piece in feedback:
grouped[piece.incident.commander.individual.email].append(piece)
if piece.incident and piece.incident.commander:
grouped[piece.incident.commander.individual.email].append(piece)
return grouped


def group_feedback_by_assignee(feedback):
"""Groups feedback by assignee."""
grouped = defaultdict(lambda: [])
for piece in feedback:
if piece.case and piece.case.assignee:
grouped[piece.case.assignee.individual.email].append(piece)
return grouped


@scheduler.add(every(1).day.at("18:00"), name="feedback-report-daily")
@timer
@scheduled_project_task
def feedback_report_daily(db_session: SessionLocal, project: Project):
def feedback_report_daily(db_session: Session, project: Project):
"""
Fetches all incident feedback provided in the last 24 hours
and sends a daily report to the commanders who handled the incidents.
Fetches all incident and case feedback provided in the last 24 hours
and sends a daily report to the commanders and assignees who handled the incidents/cases.
"""
feedback = get_all_last_x_hours_by_project_id(db_session=db_session, project_id=project.id)
incident_feedback = get_all_incident_last_x_hours_by_project_id(
db_session=db_session, project_id=project.id
)

if feedback:
grouped_feedback = group_feedback_by_commander(feedback)
for commander_email, feedback in grouped_feedback.items():
if incident_feedback:
grouped_incident_feedback = group_feedback_by_commander(incident_feedback)
for commander_email, feedback in grouped_incident_feedback.items():
send_incident_feedback_daily_report(commander_email, feedback, project.id, db_session)

case_feedback = get_all_case_last_x_hours_by_project_id(
db_session=db_session, project_id=project.id
)

if case_feedback:
grouped_case_feedback = group_feedback_by_assignee(case_feedback)
for assignee_email, feedback in grouped_case_feedback.items():
send_case_feedback_daily_report(assignee_email, feedback, project.id, db_session)
17 changes: 16 additions & 1 deletion src/dispatch/feedback/incident/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from dispatch.incident import service as incident_service
from dispatch.case import service as case_service
from dispatch.incident.models import Incident
from dispatch.case.models import Case
from dispatch.project.models import Project

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


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


def get_all_case_last_x_hours_by_project_id(
*, db_session, hours: int = 24, project_id: int
) -> List[Optional[Feedback]]:
"""Returns all feedback provided in the last x hours by project id. Defaults to 24 hours."""
return (
db_session.query(Feedback)
.join(Case)
.join(Project)
.filter(Project.id == project_id)
.filter(Feedback.created_at >= datetime.utcnow() - timedelta(hours=hours))
.all()
)


def create(*, db_session, feedback_in: FeedbackCreate) -> Feedback:
"""Creates a new piece of feedback."""
if feedback_in.incident:
Expand Down
5 changes: 5 additions & 0 deletions src/dispatch/messaging/email/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
INCIDENT_DAILY_REPORT_DESCRIPTION,
INCIDENT_FEEDBACK_DAILY_REPORT_DESCRIPTION,
INCIDENT_TASK_REMINDER_DESCRIPTION,
CASE_FEEDBACK_DAILY_REPORT_DESCRIPTION,
MessageType,
render_message_template,
)
Expand Down Expand Up @@ -42,6 +43,10 @@ def get_template(message_type: MessageType, project_id: int):
"notification_list.mjml",
INCIDENT_FEEDBACK_DAILY_REPORT_DESCRIPTION,
),
MessageType.case_feedback_daily_report: (
"notification_list.mjml",
CASE_FEEDBACK_DAILY_REPORT_DESCRIPTION,
),
MessageType.incident_daily_report: (
"notification_list.mjml",
INCIDENT_DAILY_REPORT_DESCRIPTION,
Expand Down
15 changes: 15 additions & 0 deletions src/dispatch/messaging/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class MessageType(DispatchEnum):
service_feedback = "service-feedback"
task_add_to_incident = "task-add-to-incident"
case_rating_feedback = "case-rating-feedback"
case_feedback_daily_report = "case-feedback-daily-report"


INCIDENT_STATUS_DESCRIPTIONS = {
Expand Down Expand Up @@ -80,6 +81,11 @@ class MessageType(DispatchEnum):
"\n", " "
).strip()

CASE_FEEDBACK_DAILY_REPORT_DESCRIPTION = """
This is a daily report of feedback about cases handled by you.""".replace(
"\n", " "
).strip()

INCIDENT_WEEKLY_REPORT_TITLE = """
Incidents Weekly Report""".replace(
"\n", " "
Expand Down Expand Up @@ -999,6 +1005,15 @@ class MessageType(DispatchEnum):
{"title": "Created At", "text": "", "datetime": "{{ created_at}}"},
]

CASE_FEEDBACK_DAILY_REPORT = [
{"title": "Case", "text": "{{ name }}"},
{"title": "Case Title", "text": "{{ title }}"},
{"title": "Rating", "text": "{{ rating }}"},
{"title": "Feedback", "text": "{{ feedback }}"},
{"title": "Participant", "text": "{{ participant }}"},
{"title": "Created At", "text": "", "datetime": "{{ created_at}}"},
]

INCIDENT_WEEKLY_REPORT_HEADER = {
"type": "header",
"text": INCIDENT_WEEKLY_REPORT_TITLE,
Expand Down
Loading