Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/src/dispatch/stati…
Browse files Browse the repository at this point in the history
…c/dispatch/sass-1.80.4
  • Loading branch information
whitdog47 authored Oct 26, 2024
2 parents 50e9bc0 + 8c7742a commit af1e0d9
Show file tree
Hide file tree
Showing 19 changed files with 346 additions and 110 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
11 changes: 7 additions & 4 deletions src/dispatch/case/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def case_update_flow(
# we get the case
case = get(db_session=db_session, case_id=case_id)

if reporter_email and case and reporter_email != case.reporter.email:
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,
Expand All @@ -345,7 +345,7 @@ def case_update_flow(
db_session=db_session,
)

if assignee_email and case and assignee_email != case.assignee.email:
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,
Expand Down Expand Up @@ -374,15 +374,15 @@ def case_update_flow(

if case.tactical_group:
# we update the tactical group
if reporter_email and reporter_email != case.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 and assignee_email != case.assignee.email:
if assignee_email and assignee_email != case.assignee.individual.email:
group_flows.update_group(
subject=case,
group=case.tactical_group,
Expand Down Expand Up @@ -488,6 +488,9 @@ def case_closed_status_flow(case: Case, db_session=None):
if not storage_plugin:
return

# we update the ticket
ticket_flows.update_case_ticket(case=case, db_session=db_session)

# Open document access if configured
if storage_plugin.configuration.open_on_close:
for document in case.documents:
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
13 changes: 13 additions & 0 deletions src/dispatch/plugins/dispatch_slack/case/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
SignalFilterCreate,
SignalInstance,
)
from dispatch.ticket import flows as ticket_flows

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -1008,6 +1009,10 @@ def handle_case_participant_role_activity(
organization_slug=context["subject"].organization_slug,
)
case.status = CaseStatus.triage

# we update the ticket
ticket_flows.update_case_ticket(case=case, db_session=db_session)

case_flows.update_conversation(case, db_session)
db_session.commit()

Expand Down Expand Up @@ -1073,6 +1078,10 @@ def reopen_button_click(
ack()
case = case_service.get(db_session=db_session, case_id=context["subject"].id)
case.status = CaseStatus.triage

# we update the ticket
ticket_flows.update_case_ticket(case=case, db_session=db_session)

db_session.commit()

# update case message
Expand Down Expand Up @@ -1644,6 +1653,10 @@ def triage_button_click(
)
case.status = CaseStatus.triage
db_session.commit()

# we update the ticket
ticket_flows.update_case_ticket(case=case, db_session=db_session)

case_flows.update_conversation(case, db_session)


Expand Down
2 changes: 1 addition & 1 deletion src/dispatch/static/dispatch/src/case/DetailsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
/>
</v-col>
<v-col cols="6">
<project-select v-model="project" />
<project-select v-model="project" disabled />
</v-col>
<v-col cols="6">
<case-type-select v-model="case_type" :project="project" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,22 +140,8 @@ export default {
},
watch: {
project: {
handler(newProject) {
if (newProject?.id !== this.lastProjectId) {
// Check if we're moving to a valid project (not null)
if (this.lastProjectId) {
this.lastProjectId = newProject.id
this.resetSelection()
this.fetchData()
} else {
// If new project is null/undefined, just update lastProjectId
this.lastProjectId = null
}
}
this.validatePriority()
},
deep: true,
project() {
this.fetchData()
},
},
Expand Down
Loading

0 comments on commit af1e0d9

Please sign in to comment.