Skip to content

Commit

Permalink
Create AI-generated weekly summary (#5074)
Browse files Browse the repository at this point in the history
  • Loading branch information
whitdog47 authored Sep 16, 2024
1 parent e4f18b8 commit c8ecf87
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Adds project parameter to send weekly reports
Revision ID: f729d61738b0
Revises: 0a6702319f6a
Create Date: 2024-08-12 15:22:41.977924
"""

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "f729d61738b0"
down_revision = "0a6702319f6a"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"project",
sa.Column(
"send_weekly_reports", sa.Boolean(), nullable=True, server_default=sa.text("false")
),
)
op.add_column(
"project", sa.Column("weekly_report_notification_id", sa.Integer(), nullable=True)
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("project", "send_weekly_reports")
op.drop_column("project", "weekly_report_notification_id")
# ### end Alembic commands ###
122 changes: 117 additions & 5 deletions src/dispatch/incident/scheduled.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@

from datetime import datetime, date
from schedule import every
from sqlalchemy import func
from sqlalchemy import func, Session

from dispatch.enums import Visibility
from dispatch.conversation.enums import ConversationButtonActions
from dispatch.database.core import SessionLocal, resolve_attr
from dispatch.database.core import resolve_attr
from dispatch.decorators import scheduled_project_task, timer
from dispatch.messaging.strings import (
INCIDENT,
INCIDENT_DAILY_REPORT,
INCIDENT_DAILY_REPORT_TITLE,
INCIDENT_WEEKLY_REPORT,
INCIDENT_WEEKLY_REPORT_TITLE,
INCIDENT_SUMMARY_TEMPLATE,
MessageType,
)
from dispatch.nlp import build_phrase_matcher, build_term_vocab, extract_terms_from_text
Expand All @@ -39,7 +43,7 @@
@scheduler.add(every(1).hours, name="incident-auto-tagger")
@timer
@scheduled_project_task
def incident_auto_tagger(db_session: SessionLocal, project: Project):
def incident_auto_tagger(db_session: Session, project: Project):
"""Attempts to take existing tags and associate them with incidents."""
plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=project.id, plugin_type="storage"
Expand Down Expand Up @@ -86,7 +90,7 @@ def incident_auto_tagger(db_session: SessionLocal, project: Project):
@scheduler.add(every(1).day.at("18:00"), name="incident-report-daily")
@timer
@scheduled_project_task
def incident_report_daily(db_session: SessionLocal, project: Project):
def incident_report_daily(db_session: Session, project: Project):
"""Creates and sends incident daily reports based on notifications."""

# don't send if set to false
Expand Down Expand Up @@ -209,7 +213,7 @@ def incident_report_daily(db_session: SessionLocal, project: Project):
@scheduler.add(every(1).day.at("18:00"), name="incident-close-reminder")
@timer
@scheduled_project_task
def incident_close_reminder(db_session: SessionLocal, project: Project):
def incident_close_reminder(db_session: Session, project: Project):
"""Sends a reminder to the incident commander to close out their incident."""
incidents = get_all_by_status(
db_session=db_session, project_id=project.id, status=IncidentStatus.stable
Expand All @@ -222,3 +226,111 @@ def incident_close_reminder(db_session: SessionLocal, project: Project):
# we only send the reminder for incidents that have been stable
# longer than a week and only on Mondays
send_incident_close_reminder(incident, db_session)


@scheduler.add(every().monday.at("18:00"), name="incident-report-weekly")
@timer
@scheduled_project_task
def incident_report_weekly(db_session: Session, project: Project):
"""Creates and sends incident weekly reports based on notifications."""

# don't send if set to false or no notification id is set
if project.send_weekly_reports is False or not project.weekly_report_notification_id:
return

# don't send if no enabled ai plugin
ai_plugin = plugin_service.get_active_instance(
db_session=db_session, plugin_type="artificial-intelligence", project_id=project.id
)
if not ai_plugin:
log.warning("Incident weekly reports not sent. No AI plugin enabled.")
return

# we fetch all closed incidents in the last week
incidents = get_all_last_x_hours_by_status(
db_session=db_session,
project_id=project.id,
status=IncidentStatus.closed,
hours=24 * 7,
)

# no incidents closed in the last week
if not incidents:
return

storage_plugin = plugin_service.get_active_instance(
db_session=db_session, plugin_type="storage", project_id=project.id
)

if not storage_plugin:
log.warning(
f"Incident weekly reports not sent. No storage plugin enabled. Project: {project.name}."
)
return

# we create and send an incidents weekly report
for incident in incidents:
items_grouped = []
items_grouped_template = INCIDENT_SUMMARY_TEMPLATE

# Skip restricted incidents
if incident.visibility == Visibility.restricted:
continue
try:
pir_doc = storage_plugin.instance.get(
file_id=incident.incident_review_document.resource_id,
mime_type="text/plain",
)
messages = {
"role": "user",
"content": """Given the text of the security post-incident review document below,
provide answers to the following questions:
1. What is the summary of what happened?
2. What were the overall risk(s)?
3. How were the risk(s) mitigated?
4. How was the incident resolved?
5. What are the follow-up tasks?
"""
+ pir_doc,
}

response = ai_plugin.instance.chat(messages)
summary = response["choices"][0]["message"]["content"]

item = {
"commander_fullname": incident.commander.individual.name,
"commander_team": incident.commander.team,
"commander_weblink": incident.commander.individual.weblink,
"name": incident.name,
"ticket_weblink": resolve_attr(incident, "ticket.weblink"),
"title": incident.title,
"summary": summary,
}

items_grouped.append(item)
except Exception as e:
log.exception(e)

notification_kwargs = {
"items_grouped": items_grouped,
"items_grouped_template": items_grouped_template,
}

notification_title_text = f"{project.name} {INCIDENT_WEEKLY_REPORT_TITLE}"
notification_params = {
"text": notification_title_text,
"type": MessageType.incident_weekly_report,
"template": INCIDENT_WEEKLY_REPORT,
"kwargs": notification_kwargs,
}

notification = notification_service.get(
db_session=db_session, notification_id=project.weekly_report_notification_id
)

notification_service.send(
db_session=db_session,
project_id=notification.project.id,
notification=notification,
notification_params=notification_params,
)
48 changes: 48 additions & 0 deletions src/dispatch/messaging/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class MessageType(DispatchEnum):
incident_closed_information_review_reminder = "incident-closed-information-review-reminder"
incident_completed_form_notification = "incident-completed-form-notification"
incident_daily_report = "incident-daily-report"
incident_weekly_report = "incident-weekly-report"
incident_executive_report = "incident-executive-report"
incident_feedback_daily_report = "incident-feedback-daily-report"
incident_management_help_tips = "incident-management-help-tips"
Expand Down Expand Up @@ -78,6 +79,18 @@ class MessageType(DispatchEnum):
"\n", " "
).strip()

INCIDENT_WEEKLY_REPORT_TITLE = """
Incidents Weekly Report""".replace(
"\n", " "
).strip()

INCIDENT_WEEKLY_REPORT_DESCRIPTION = """
This is an AI-generated weekly summary of incidents that have been marked as closed in the last week.
NOTE: These summaries may contain errors or inaccuracies.
Please verify the information before relying on it.""".replace(
"\n", " "
).strip()

INCIDENT_DAILY_REPORT_TITLE = """
Incidents Daily Report""".replace(
"\n", " "
Expand Down Expand Up @@ -521,6 +534,14 @@ class MessageType(DispatchEnum):
"text": NOTIFICATION_PURPOSES_FYI,
}

INCIDENT_NAME_SUMMARY = {
"title": "{{name}} Incident Summary",
"title_link": "{{ticket_weblink}}",
"text": "{{ignore}}",
}

INCIDENT_SUMMARY = {"title": "Summary", "text": "{{summary}}"}

INCIDENT_TITLE = {"title": "Title", "text": "{{title}}"}

CASE_TITLE = {"title": "Title", "text": "{{title}}"}
Expand Down Expand Up @@ -589,6 +610,12 @@ class MessageType(DispatchEnum):
"text": INCIDENT_COMMANDER_DESCRIPTION,
}

INCIDENT_COMMANDER_SUMMARY = {
"title": "Commander - {{commander_fullname}}, {{commander_team}}",
"title_link": "{{commander_weblink}}",
"text": "{{ignore}}",
}

INCIDENT_CONFERENCE = {
"title": "Conference",
"title_link": "{{conference_weblink}}",
Expand Down Expand Up @@ -948,6 +975,15 @@ class MessageType(DispatchEnum):
{"title": "Created At", "text": "", "datetime": "{{ created_at}}"},
]

INCIDENT_WEEKLY_REPORT_HEADER = {
"type": "header",
"text": INCIDENT_WEEKLY_REPORT_TITLE,
}

INCIDENT_WEEKLY_REPORT_HEADER_DESCRIPTION = {
"text": INCIDENT_WEEKLY_REPORT_DESCRIPTION,
}

INCIDENT_DAILY_REPORT_HEADER = {
"type": "header",
"text": INCIDENT_DAILY_REPORT_TITLE,
Expand All @@ -968,6 +1004,12 @@ class MessageType(DispatchEnum):
INCIDENT_DAILY_REPORT_FOOTER,
]

INCIDENT_WEEKLY_REPORT = [
INCIDENT_WEEKLY_REPORT_HEADER,
INCIDENT_WEEKLY_REPORT_HEADER_DESCRIPTION,
INCIDENT_DAILY_REPORT_FOOTER,
]

INCIDENT = [
INCIDENT_NAME_WITH_ENGAGEMENT_NO_DESCRIPTION,
INCIDENT_TITLE,
Expand All @@ -978,6 +1020,12 @@ class MessageType(DispatchEnum):
INCIDENT_COMMANDER,
]

INCIDENT_SUMMARY_TEMPLATE = [
INCIDENT_NAME_SUMMARY,
INCIDENT_TITLE,
INCIDENT_COMMANDER_SUMMARY,
INCIDENT_SUMMARY,
]

INCIDENT_MANAGEMENT_HELP_TIPS_MESSAGE = [
{
Expand Down
7 changes: 7 additions & 0 deletions src/dispatch/project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class Project(Base):
allow_self_join = Column(Boolean, default=True, server_default="t")

send_daily_reports = Column(Boolean)
send_weekly_reports = Column(Boolean)

weekly_report_notification_id = Column(Integer, nullable=True)

select_commander_visibility = Column(Boolean, default=True, server_default="t")

Expand Down Expand Up @@ -82,6 +85,8 @@ class ProjectBase(DispatchBase):
default: bool = False
color: Optional[str] = Field(None, nullable=True)
send_daily_reports: Optional[bool] = Field(True, nullable=True)
send_weekly_reports: Optional[bool] = Field(False, nullable=True)
weekly_report_notification_id: Optional[int] = Field(None, nullable=True)
enabled: Optional[bool] = Field(True, nullable=True)
storage_folder_one: Optional[str] = Field(None, nullable=True)
storage_folder_two: Optional[str] = Field(None, nullable=True)
Expand All @@ -97,6 +102,8 @@ class ProjectCreate(ProjectBase):

class ProjectUpdate(ProjectBase):
send_daily_reports: Optional[bool] = Field(True, nullable=True)
send_weekly_reports: Optional[bool] = Field(False, nullable=True)
weekly_report_notification_id: Optional[int] = Field(None, nullable=True)
stable_priority_id: Optional[int]


Expand Down
54 changes: 53 additions & 1 deletion src/dispatch/static/dispatch/src/notification/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,49 @@
</v-tooltip>
</v-col>
</v-row>
<v-row align="start" no-gutters>
<v-col class="d-flex justify-start" cols="4">
<v-checkbox
class="ml-10 mr-5"
v-model="weeklyReports"
@update:model-value="updateWeeklyReports"
:disabled="weeklyReports == null"
>
<template #label>
<div>
<div>Send Weekly Incident Summary</div>
<small class="text-subtext">
(requires enabled artificial-intelligence plugin)
</small>
</div>
</template>
</v-checkbox>
<v-tooltip max-width="500px" open-delay="50" location="bottom">
<template #activator="{ props }">
<v-icon v-bind="props"> mdi-information </v-icon>
</template>
<span>
If activated, Dispatch will send a weekly summary report of incidents that were
marked as closed in the last week.
</span>
</v-tooltip>
</v-col>
<v-col cols="5">
<v-select
:disabled="!weeklyReports"
v-model="weeklyReportNotificationId"
:items="items"
item-title="name"
item-value="id"
@update:model-value="updateWeeklyReportNotificationId"
:menu-props="{ maxHeight: '400' }"
label="Target notification channel"
clearable
chips
hint="Set the notification channel for the weekly report."
/>
</v-col>
</v-row>
</v-col>
</v-row>
</v-container>
Expand Down Expand Up @@ -151,6 +194,8 @@ export default {
"table.rows.items",
"table.rows.total",
"dailyReports",
"weeklyReports",
"weeklyReportNotificationId",
]),
},
Expand All @@ -177,7 +222,14 @@ export default {
},
methods: {
...mapActions("notification", ["getAll", "createEditShow", "removeShow", "updateDailyReports"]),
...mapActions("notification", [
"getAll",
"createEditShow",
"removeShow",
"updateDailyReports",
"updateWeeklyReports",
"updateWeeklyReportNotificationId",
]),
},
}
</script>
Loading

0 comments on commit c8ecf87

Please sign in to comment.