Skip to content

Commit

Permalink
Merge branch 'master' into bugfix/ensure-case-ticket-updated
Browse files Browse the repository at this point in the history
  • Loading branch information
whitdog47 authored Oct 26, 2024
2 parents 681ed09 + 2dde1cc commit e9d08e6
Show file tree
Hide file tree
Showing 21 changed files with 358 additions and 120 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
# Minimum code coverage per file
COVERAGE_SINGLE: 50
# Minimum total code coverage
COVERAGE_TOTAL: 56
COVERAGE_TOTAL: 55
runs-on: ubuntu-latest
services:
postgres:
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ default_language_version:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# ruff version.
rev: v0.6.4
rev: v0.7.0
hooks:
# Run the linter.
#
Expand All @@ -28,7 +28,7 @@ repos:

# Typos
- repo: https://github.com/crate-ci/typos
rev: v1.24.5
rev: v1.26.1
hooks:
- id: typos
exclude: ^(data/dispatch-sample-data.dump|src/dispatch/static/dispatch/src/|src/dispatch/database/revisions/)
Expand Down
23 changes: 11 additions & 12 deletions src/dispatch/case/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,34 @@
from dispatch.database.core import SessionLocal
from dispatch.decorators import background_task
from dispatch.document import flows as document_flows
from dispatch.enums import DocumentResourceTypes, Visibility, EventType
from dispatch.enums import DocumentResourceTypes, EventType, Visibility
from dispatch.event import service as event_service
from dispatch.group import flows as group_flows
from dispatch.group.enums import GroupAction, GroupType
from dispatch.incident import flows as incident_flows
from dispatch.incident import service as incident_service
from dispatch.incident.enums import IncidentStatus
from dispatch.incident.messaging import send_participant_announcement_message
from dispatch.incident.models import IncidentCreate, Incident
from dispatch.incident.type.models import IncidentType
from dispatch.incident.models import Incident, IncidentCreate
from dispatch.incident.priority.models import IncidentPriority
from dispatch.incident.type.models import IncidentType
from dispatch.individual.models import IndividualContactRead
from dispatch.models import OrganizationSlug, PrimaryKey
from dispatch.participant import flows as participant_flows
from dispatch.participant import service as participant_service
from dispatch.participant.models import ParticipantUpdate
from dispatch.participant_role import flows as role_flow
from dispatch.participant_role.models import ParticipantRoleType, ParticipantRole
from dispatch.participant_role.models import ParticipantRole, ParticipantRoleType
from dispatch.plugin import service as plugin_service
from dispatch.storage import flows as storage_flows
from dispatch.storage.enums import StorageAction
from dispatch.ticket import flows as ticket_flows

from .messaging import (
send_case_created_notifications,
send_case_update_notifications,
send_case_rating_feedback_message,
send_case_update_notifications,
)

from .models import Case, CaseStatus
from .service import get

Expand Down Expand Up @@ -337,17 +336,17 @@ def case_update_flow(
# we get the case
case = get(db_session=db_session, case_id=case_id)

if reporter_email:
# we run the case assign role flow for the reporter
if reporter_email and case and reporter_email != case.reporter.individual.email:
# we run the case assign role flow for the reporter if it changed
case_assign_role_flow(
case_id=case.id,
participant_email=reporter_email,
participant_role=ParticipantRoleType.reporter,
db_session=db_session,
)

if assignee_email:
# we run the case assign role flow for the assignee
if assignee_email and case and assignee_email != case.assignee.individual.email:
# we run the case assign role flow for the assignee if it changed
case_assign_role_flow(
case_id=case.id,
participant_email=assignee_email,
Expand Down Expand Up @@ -375,15 +374,15 @@ def case_update_flow(

if case.tactical_group:
# we update the tactical group
if reporter_email:
if reporter_email and reporter_email != case.reporter.individual.email:
group_flows.update_group(
subject=case,
group=case.tactical_group,
group_action=GroupAction.add_member,
group_member=reporter_email,
db_session=db_session,
)
if assignee_email:
if assignee_email and assignee_email != case.assignee.individual.email:
group_flows.update_group(
subject=case,
group=case.tactical_group,
Expand Down
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
4 changes: 3 additions & 1 deletion src/dispatch/plugins/dispatch_google/groups/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def add_member(client: Any, group_key: str, email: str, role: str):
def remove_member(client: Any, group_key: str, email: str):
"""Removes member from google group."""
try:
return make_call(client.members(), "delete", groupKey=group_key, memberKey=email)
return make_call(
client.members(), "delete", groupKey=group_key, memberKey=email, propagate_errors=True
)
except HttpError as e:
if e.resp.status in [409]:
log.debug(
Expand Down
Loading

0 comments on commit e9d08e6

Please sign in to comment.