def send_welcome_email_to_participant( participant_email: str, incident_id: int, db_session: SessionLocal ): """Sends a welcome email to the participant.""" # we load the incident instance plugin = plugin_service.get_active(db_session=db_session, plugin_type="email") if not plugin: log.warning("Participant welcome email not sent, not email plugin configured.") return incident = incident_service.get(db_session=db_session, incident_id=incident_id) message_kwargs = { "name": incident.name, "title": incident.title, "description": incident.description, "status": incident.status, "type": incident.incident_type.name, "type_description": incident.incident_type.description, "priority": incident.incident_priority.name, "priority_description": incident.incident_priority.description, "commander_fullname": incident.commander.individual.name, "commander_team": incident.commander.team, "commander_weblink": incident.commander.individual.weblink, "reporter_fullname": incident.reporter.individual.name, "reporter_team": incident.reporter.team, "reporter_weblink": incident.reporter.individual.weblink, "document_weblink": resolve_attr(incident, "incident_document.weblink"), "storage_weblink": resolve_attr(incident, "storage.weblink"), "ticket_weblink": resolve_attr(incident, "ticket.weblink"), "conference_weblink": resolve_attr(incident, "conference.weblink"), "conference_challenge": resolve_attr(incident, "conference.conference_challenge"), "contact_fullname": incident.commander.individual.name, "contact_weblink": incident.commander.individual.weblink, } faq_doc = document_service.get_incident_faq_document(db_session=db_session) if faq_doc: message_kwargs.update({"faq_weblink": faq_doc.weblink}) conversation_reference = document_service.get_conversation_reference_document( db_session=db_session ) if conversation_reference: message_kwargs.update( {"conversation_commands_reference_document_weblink": conversation_reference.weblink} ) notification_text = "Incident Notification" plugin.instance.send( participant_email, notification_text, INCIDENT_PARTICIPANT_WELCOME_MESSAGE, MessageType.incident_participant_welcome, **message_kwargs, ) log.debug(f"Welcome email sent to {participant_email}.")
def send_incident_resources_ephemeral_message_to_participant( user_id: str, incident_id: int, db_session: SessionLocal ): """Sends the list of incident resources to the participant via an ephemeral message.""" # we load the incident instance plugin = plugin_service.get_active(db_session=db_session, plugin_type="conversation") if not plugin: log.warning("Incident resource message not sent, no conversation plugin enabled.") return incident = incident_service.get(db_session=db_session, incident_id=incident_id) message_kwargs = { "title": incident.title, "description": incident.description, "commander_fullname": incident.commander.individual.name, "commander_team": incident.commander.team, "commander_weblink": incident.commander.individual.weblink, "reporter_fullname": incident.reporter.individual.name, "reporter_team": incident.reporter.team, "reporter_weblink": incident.reporter.individual.weblink, "document_weblink": resolve_attr(incident, "incident_document.weblink"), "storage_weblink": resolve_attr(incident, "storage.weblink"), "ticket_weblink": resolve_attr(incident, "ticket.weblink"), "conference_weblink": resolve_attr(incident, "conference.weblink"), "conference_challenge": resolve_attr(incident, "conference.conference_challenge"), } if incident.incident_review_document: message_kwargs.update( {"review_document_weblink": incident.incident_review_document.weblink} ) faq_doc = document_service.get_incident_faq_document(db_session=db_session) if faq_doc: message_kwargs.update({"faq_weblink": faq_doc.weblink}) conversation_reference = document_service.get_conversation_reference_document( db_session=db_session ) if conversation_reference: message_kwargs.update( {"conversation_commands_reference_document_weblink": conversation_reference.weblink} ) # we send the ephemeral message plugin.instance.send_ephemeral( incident.conversation.channel_id, user_id, "Incident Resources Message", INCIDENT_RESOURCES_MESSAGE, MessageType.incident_resources_message, **message_kwargs, ) log.debug(f"List of incident resources sent to {user_id} via ephemeral message.")
def send_welcome_ephemeral_message_to_participant( participant_email: str, incident_id: int, db_session: SessionLocal ): """Sends an ephemeral message to the participant.""" # we load the incident instance plugin = plugin_service.get_active(db_session=db_session, plugin_type="conversation") if not plugin: log.warning("Incident welcome message not sent, not conversation plugin enabled.") return incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we send the ephemeral message message_kwargs = { "name": incident.name, "title": incident.title, "status": incident.status, "type": incident.incident_type.name, "type_description": incident.incident_type.description, "priority": incident.incident_priority.name, "priority_description": incident.incident_priority.description, "commander_fullname": incident.commander.individual.name, "commander_weblink": incident.commander.individual.weblink, "document_weblink": resolve_attr(incident, "incident_document.weblink"), "storage_weblink": resolve_attr(incident, "storage.weblink"), "ticket_weblink": resolve_attr(incident, "ticket.weblink"), "conference_weblink": resolve_attr(incident, "conference.weblink"), "conference_challenge": resolve_attr(incident, "conference.conference_challenge"), } faq_doc = document_service.get_incident_faq_document(db_session=db_session) if faq_doc: message_kwargs.update({"faq_weblink": faq_doc.weblink}) conversation_reference = document_service.get_conversation_reference_document( db_session=db_session ) if conversation_reference: message_kwargs.update( {"conversation_commands_reference_document_weblink": conversation_reference.weblink} ) plugin.instance.send_ephemeral( incident.conversation.channel_id, participant_email, "Incident Welcome Message", INCIDENT_PARTICIPANT_WELCOME_MESSAGE, MessageType.incident_participant_welcome, **message_kwargs, ) log.debug(f"Welcome ephemeral message sent to {participant_email}.")
def send_incident_created_notifications(incident: Incident, db_session: SessionLocal): """Sends incident created notifications.""" notification_template = INCIDENT_NOTIFICATION.copy() if incident.status != IncidentStatus.closed: notification_template.insert(0, INCIDENT_NAME_WITH_ENGAGEMENT) else: notification_template.insert(0, INCIDENT_NAME) notification_kwargs = { "name": incident.name, "title": incident.title, "description": incident.description, "status": incident.status, "type": incident.incident_type.name, "type_description": incident.incident_type.description, "priority": incident.incident_priority.name, "priority_description": incident.incident_priority.description, "reporter_fullname": incident.reporter.individual.name, "reporter_team": incident.reporter.team, "reporter_weblink": incident.reporter.individual.weblink, "commander_fullname": incident.commander.individual.name, "commander_team": incident.commander.team, "commander_weblink": incident.commander.individual.weblink, "document_weblink": resolve_attr(incident, "incident_document.weblink"), "storage_weblink": resolve_attr(incident, "storage.weblink"), "ticket_weblink": resolve_attr(incident, "ticket.weblink"), "conference_weblink": resolve_attr(incident, "conference.weblink"), "conference_challenge": resolve_attr(incident, "conference.conference_challenge"), "contact_fullname": incident.commander.individual.name, "contact_weblink": incident.commander.individual.weblink, "incident_id": incident.id, } faq_doc = document_service.get_incident_faq_document(db_session=db_session) if faq_doc: notification_kwargs.update({"faq_weblink": faq_doc.weblink}) notification_params = { "text": "Incident Notification", "type": MessageType.incident_notification, "template": notification_template, "kwargs": notification_kwargs, } notification_service.filter_and_send( db_session=db_session, class_instance=incident, notification_params=notification_params ) log.debug("Incident created notifications sent.")
def send_incident_report_reminder(incident: Incident, report_type: ReportTypes, db_session: SessionLocal): """Sends a direct message to the incident commander indicating that they should complete a report.""" message_text = f"Incident {report_type.value} Reminder" message_template = INCIDENT_REPORT_REMINDER command_name, message_type = get_report_reminder_settings(report_type) plugin = plugin_service.get_active(db_session=db_session, plugin_type="conversation") if not plugin: log.warning( "Incident report reminder not sent, no conversation plugin enabled." ) return report_command = plugin.instance.get_command_name(command_name) ticket_weblink = resolve_attr(incident, "ticket.weblink") items = [{ "command": report_command, "name": incident.name, "report_type": report_type.value, "ticket_weblink": ticket_weblink, "title": incident.title, }] plugin.instance.send_direct( incident.commander.email, message_text, message_template, message_type, items=items, ) log.debug(f"Incident report reminder sent to {incident.commander.email}.")
def list_incidents(incident_id: int, command: dict = None, db_session=None): """Returns the list of current active and stable incidents, and closed incidents in the last 24 hours.""" incidents = [] # We fetch active incidents incidents = incident_service.get_all_by_status( db_session=db_session, status=IncidentStatus.active.value) # We fetch stable incidents incidents.extend( incident_service.get_all_by_status(db_session=db_session, status=IncidentStatus.stable.value)) # We fetch closed incidents in the last 24 hours incidents.extend( incident_service.get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.closed.value, hours=24)) blocks = [] blocks.append({ "type": "header", "text": { "type": "plain_text", "text": "List of Incidents" } }) if incidents: for incident in incidents: if incident.visibility == Visibility.open: ticket_weblink = resolve_attr(incident, "ticket.weblink") try: blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": (f"*<{ticket_weblink}|{incident.name}>*\n" f"*Title*: {incident.title}\n" f"*Type*: {incident.incident_type.name}\n" f"*Priority*: {incident.incident_priority.name}\n" f"*Status*: {incident.status}\n" f"*Incident Commander*: <{incident.commander.individual.weblink}|{incident.commander.individual.name}>" ), }, }) except Exception as e: log.exception(e) dispatch_slack_service.send_ephemeral_message( slack_client, command["channel_id"], command["user_id"], "Incident List", blocks=blocks, )
def update_external_incident_ticket( incident: Incident, db_session: SessionLocal, ): """Update external incident ticket.""" plugin = plugin_service.get_active(db_session=db_session, plugin_type="ticket") if not plugin: log.warning("External ticket not updated, no ticket plugin enabled.") return title = incident.title description = incident.description if incident.visibility == Visibility.restricted: title = description = incident.incident_type.name incident_type_plugin_metadata = incident_type_service.get_by_name( db_session=db_session, name=incident.incident_type.name).get_meta(plugin.slug) plugin.instance.update( incident.ticket.resource_id, title, description, incident.incident_type.name, incident.incident_priority.name, incident.status.lower(), incident.commander.email, incident.reporter.email, resolve_attr(incident, "conversation.weblink"), resolve_attr(incident, "incident_document.weblink"), resolve_attr(incident, "storage.weblink"), resolve_attr(incident, "conference.weblink"), incident.cost, incident_type_plugin_metadata=incident_type_plugin_metadata, ) log.debug("The external ticket has been updated.")
def send_incident_update_notifications(incident: Incident, previous_incident: IncidentRead, db_session: SessionLocal): """Sends notifications about incident changes.""" notification_text = "Incident Notification" notification_type = MessageType.incident_notification notification_template = INCIDENT_NOTIFICATION_COMMON.copy() change = False if previous_incident.status != incident.status: change = True notification_template.append(INCIDENT_STATUS_CHANGE) if previous_incident.incident_type.name != incident.incident_type.name: change = True notification_template.append(INCIDENT_TYPE_CHANGE) if previous_incident.incident_priority.name != incident.incident_priority.name: change = True notification_template.append(INCIDENT_PRIORITY_CHANGE) if not change: # we don't need to notify log.debug("Incident change notifications not sent.") return notification_template.append(INCIDENT_COMMANDER) convo_plugin = plugin_service.get_active(db_session=db_session, plugin_type="conversation") # we send an update to the incident conversation incident_conversation_notification_template = notification_template.copy() incident_conversation_notification_template.insert(0, INCIDENT_NAME) # we can't send messages to closed channels if incident.status != IncidentStatus.closed: convo_plugin.instance.send( incident.conversation.channel_id, notification_text, incident_conversation_notification_template, notification_type, name=incident.name, ticket_weblink=incident.ticket.weblink, title=incident.title, incident_type_old=previous_incident.incident_type.name, incident_type_new=incident.incident_type.name, incident_priority_old=previous_incident.incident_priority.name, incident_priority_new=incident.incident_priority.name, incident_status_old=previous_incident.status.value, incident_status_new=incident.status, commander_fullname=incident.commander.name, commander_weblink=incident.commander.weblink, ) if incident.visibility == Visibility.open: notification_conversation_notification_template = notification_template.copy( ) if incident.status != IncidentStatus.closed: notification_conversation_notification_template.insert( 0, INCIDENT_NAME_WITH_ENGAGEMENT) else: notification_conversation_notification_template.insert( 0, INCIDENT_NAME) # we send an update to the incident notification conversations for conversation in INCIDENT_NOTIFICATION_CONVERSATIONS: convo_plugin.instance.send( conversation, notification_text, notification_conversation_notification_template, notification_type, name=incident.name, ticket_weblink=incident.ticket.weblink, title=incident.title, incident_id=incident.id, incident_type_old=previous_incident.incident_type.name, incident_type_new=incident.incident_type.name, incident_priority_old=previous_incident.incident_priority.name, incident_priority_new=incident.incident_priority.name, incident_status_old=previous_incident.status.value, incident_status_new=incident.status, commander_fullname=incident.commander.name, commander_weblink=incident.commander.weblink, ) # we send an update to the incident notification distribution lists email_plugin = plugin_service.get_active(db_session=db_session, plugin_type="email") for distro in INCIDENT_NOTIFICATION_DISTRIBUTION_LISTS: email_plugin.instance.send( distro, notification_template, notification_type, name=incident.name, title=incident.title, status=incident.status, priority=incident.incident_priority.name, priority_description=incident.incident_priority.description, commander_fullname=incident.commander.name, commander_weblink=incident.commander.weblink, document_weblink=resolve_attr(incident, "incident_document.weblink"), storage_weblink=resolve_attr(incident, "storage.weblink"), ticket_weblink=resolve_attr(incident, "ticket.weblink"), incident_id=incident.id, incident_priority_old=previous_incident.incident_priority.name, incident_priority_new=incident.incident_priority.name, incident_type_old=previous_incident.incident_type.name, incident_type_new=incident.incident_type.name, incident_status_old=previous_incident.status.value, incident_status_new=incident.status, contact_fullname=incident.commander.name, contact_weblink=incident.commander.weblink, ) log.debug("Incident update notifications sent.")
def send_incident_status_notifications(incident: Incident, db_session: SessionLocal): """Sends incident status notifications to conversations and distribution lists.""" notification_text = "Incident Notification" notification_type = MessageType.incident_notification message_template = INCIDENT_NOTIFICATION.copy() # we send status notifications to conversations if incident.status != IncidentStatus.closed: message_template.insert(0, INCIDENT_NAME_WITH_ENGAGEMENT) else: message_template.insert(0, INCIDENT_NAME) message_kwargs = { "name": incident.name, "title": incident.title, "status": incident.status, "type": incident.incident_type.name, "type_description": incident.incident_type.description, "priority": incident.incident_priority.name, "priority_description": incident.incident_priority.description, "commander_fullname": incident.commander.name, "commander_weblink": incident.commander.weblink, "document_weblink": resolve_attr(incident, "incident_document.weblink"), "storage_weblink": resolve_attr(incident, "storage.weblink"), "ticket_weblink": resolve_attr(incident, "ticket.weblink"), "conference_weblink": resolve_attr(incident, "conference.weblink"), "conference_challenge": resolve_attr(incident, "conference.conference_challenge"), "contact_fullname": incident.commander.name, "contact_weblink": incident.commander.weblink, "incident_id": incident.id, } faq_doc = document_service.get_incident_faq_document(db_session=db_session) if faq_doc: message_kwargs.update({"faq_weblink": faq_doc.weblink}) convo_plugin = plugin_service.get_active(db_session=db_session, plugin_type="conversation") if convo_plugin: for conversation in INCIDENT_NOTIFICATION_CONVERSATIONS: convo_plugin.instance.send( conversation, notification_text, message_template, notification_type, **message_kwargs, ) else: log.warning( "No incident conversation notifications sent. No conversation plugin is active." ) # we send status notifications to distribution lists email_plugin = plugin_service.get_active(db_session=db_session, plugin_type="email") if email_plugin: for distro in INCIDENT_NOTIFICATION_DISTRIBUTION_LISTS: email_plugin.instance.send(distro, message_template, notification_type, **message_kwargs) else: log.warning( "No incident email notifications sent. No email plugin is active.") log.debug("Incident status notifications sent.")
def send_incident_update_notifications( incident: Incident, previous_incident: IncidentRead, db_session: SessionLocal ): """Sends notifications about incident changes.""" notification_text = "Incident Notification" notification_type = MessageType.incident_notification notification_template = INCIDENT_NOTIFICATION_COMMON.copy() change = False if previous_incident.status != incident.status: change = True notification_template.append(INCIDENT_STATUS_CHANGE) if previous_incident.incident_type.name != incident.incident_type.name: change = True notification_template.append(INCIDENT_TYPE_CHANGE) if previous_incident.incident_priority.name != incident.incident_priority.name: change = True notification_template.append(INCIDENT_PRIORITY_CHANGE) if not change: # we don't need to notify log.debug("Incident updated notifications not sent.") return notification_template.append(INCIDENT_COMMANDER) # we send an update to the incident conversation if the incident is active or stable if incident.status != IncidentStatus.closed: incident_conversation_notification_template = notification_template.copy() incident_conversation_notification_template.insert(0, INCIDENT_NAME) convo_plugin = plugin_service.get_active(db_session=db_session, plugin_type="conversation") if convo_plugin: convo_plugin.instance.send( incident.conversation.channel_id, notification_text, incident_conversation_notification_template, notification_type, commander_fullname=incident.commander.individual.name, commander_team=incident.commander.team, commander_weblink=incident.commander.individual.weblink, incident_priority_new=incident.incident_priority.name, incident_priority_old=previous_incident.incident_priority.name, incident_status_new=incident.status, incident_status_old=previous_incident.status.value, incident_type_new=incident.incident_type.name, incident_type_old=previous_incident.incident_type.name, name=incident.name, ticket_weblink=incident.ticket.weblink, title=incident.title, ) else: log.debug( "Incident updated notification not sent to incident conversation. No conversation plugin enabled." ) # we send a notification to the notification conversations and emails fyi_notification_template = notification_template.copy() if incident.status != IncidentStatus.closed: fyi_notification_template.insert(0, INCIDENT_NAME_WITH_ENGAGEMENT) else: fyi_notification_template.insert(0, INCIDENT_NAME) notification_kwargs = { "commander_fullname": incident.commander.individual.name, "commander_team": incident.commander.team, "commander_weblink": incident.commander.individual.weblink, "contact_fullname": incident.commander.individual.name, "contact_weblink": incident.commander.individual.weblink, "incident_id": incident.id, "incident_priority_new": incident.incident_priority.name, "incident_priority_old": previous_incident.incident_priority.name, "incident_status_new": incident.status, "incident_status_old": previous_incident.status.value, "incident_type_new": incident.incident_type.name, "incident_type_old": previous_incident.incident_type.name, "name": incident.name, "ticket_weblink": resolve_attr(incident, "ticket.weblink"), "title": incident.title, } notification_params = { "text": notification_text, "type": notification_type, "template": fyi_notification_template, "kwargs": notification_kwargs, } notification_service.filter_and_send( db_session=db_session, class_instance=incident, notification_params=notification_params ) log.debug("Incident updated notifications sent.")
def daily_summary(db_session=None): """ Fetches all active, and stable and closed incidents in the last 24 hours and sends a daily summary to all incident notification conversations. """ blocks = [] blocks.append( { "type": "header", "text": { "type": "plain_text", "text": f"{INCIDENT_DAILY_SUMMARY_DESCRIPTION}", }, } ) active_incidents = get_all_by_status(db_session=db_session, status=IncidentStatus.active) if active_incidents: blocks.append( { "type": "section", "text": { "type": "mrkdwn", "text": f"*{INCIDENT_DAILY_SUMMARY_ACTIVE_INCIDENTS_DESCRIPTION}*", }, } ) for idx, incident in enumerate(active_incidents): ticket_weblink = resolve_attr(incident, "ticket.weblink") if incident.visibility == Visibility.open: try: blocks.append( { "type": "section", "text": { "type": "mrkdwn", "text": ( f"*<{ticket_weblink}|{incident.name}>*\n" f"*Title*: {incident.title}\n" f"*Type*: {incident.incident_type.name}\n" f"*Priority*: {incident.incident_priority.name}\n" f"*Incident Commander*: <{incident.commander.weblink}|{incident.commander.name}>" ), }, "block_id": f"{ConversationButtonActions.invite_user}-active-{idx}", "accessory": { "type": "button", "text": {"type": "plain_text", "text": "Join Incident"}, "value": f"{incident.id}", }, } ) except Exception as e: log.exception(e) blocks.append( { "type": "context", "elements": [ { "type": "mrkdwn", "text": f"For more information about active incidents, please visit the active incidents status <{DISPATCH_UI_URL}/incidents/status|page>.", } ], } ) else: blocks.append( { "type": "section", "text": { "type": "mrkdwn", "text": INCIDENT_DAILY_SUMMARY_NO_ACTIVE_INCIDENTS_DESCRIPTION, }, } ) blocks.append({"type": "divider"}) blocks.append( { "type": "section", "text": { "type": "mrkdwn", "text": f"*{INCIDENT_DAILY_SUMMARY_STABLE_CLOSED_INCIDENTS_DESCRIPTION}*", }, } ) hours = 24 stable_incidents = get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.stable, hours=hours ) closed_incidents = get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.closed, hours=hours ) if stable_incidents or closed_incidents: for idx, incident in enumerate(stable_incidents): ticket_weblink = resolve_attr(incident, "ticket.weblink") if incident.visibility == Visibility.open: try: blocks.append( { "type": "section", "text": { "type": "mrkdwn", "text": ( f"*<{ticket_weblink}|{incident.name}>*\n" f"*Title*: {incident.title}\n" f"*Type*: {incident.incident_type.name}\n" f"*Priority*: {incident.incident_priority.name}\n" f"*Incident Commander*: <{incident.commander.weblink}|{incident.commander.name}>\n" f"*Status*: {incident.status}" ), }, "block_id": f"{ConversationButtonActions.invite_user}-stable-{idx}", "accessory": { "type": "button", "text": {"type": "plain_text", "text": "Join Incident"}, "value": f"{incident.id}", }, } ) except Exception as e: log.exception(e) for incident in closed_incidents: ticket_weblink = resolve_attr(incident, "ticket.weblink") if incident.visibility == Visibility.open: try: blocks.append( { "type": "section", "text": { "type": "mrkdwn", "text": ( f"*<{ticket_weblink}|{incident.name}>*\n" f"*Title*: {incident.title}\n" f"*Type*: {incident.incident_type.name}\n" f"*Priority*: {incident.incident_priority.name}\n" f"*Incident Commander*: <{incident.commander.weblink}|{incident.commander.name}>\n" f"*Status*: {incident.status}" ), }, } ) except Exception as e: log.exception(e) else: blocks.append( { "type": "section", "text": { "type": "mrkdwn", "text": INCIDENT_DAILY_SUMMARY_NO_STABLE_CLOSED_INCIDENTS_DESCRIPTION, }, } ) # NOTE INCIDENT_ONCALL_SERVICE_ID is optional if INCIDENT_ONCALL_SERVICE_ID: oncall_service = service_service.get_by_external_id( db_session=db_session, external_id=INCIDENT_ONCALL_SERVICE_ID ) if not oncall_service: log.warning( "INCIDENT_ONCALL_SERVICE_ID configured in the .env file, but not found in the database. Did you create the oncall service in the UI?" ) return oncall_plugin = plugin_service.get_active(db_session=db_session, plugin_type="oncall") if not oncall_plugin: log.warning( f"Unable to resolve the oncall, INCIDENT_ONCALL_SERVICE_ID configured, but associated plugin ({oncall_plugin.slug}) is not enabled." ) return if oncall_plugin.slug != oncall_service.type: log.warning( f"Unable to resolve the oncall. Oncall plugin enabled not of type {oncall_plugin.slug}." ) return oncall_email = oncall_plugin.instance.get(service_id=INCIDENT_ONCALL_SERVICE_ID) oncall_individual = individual_service.resolve_user_by_email(oncall_email, db_session) blocks.append( { "type": "context", "elements": [ { "type": "mrkdwn", "text": f"For any questions about this notification, please reach out to <{oncall_individual['weblink']}|{oncall_individual['fullname']}> (current on-call)", } ], } ) plugin = plugin_service.get_active(db_session=db_session, plugin_type="conversation") for c in INCIDENT_NOTIFICATION_CONVERSATIONS: plugin.instance.send(c, "Incident Daily Summary", {}, "", blocks=blocks)
def daily_report(db_session=None): """ Creates and sends incident daily reports based on notifications. """ # we fetch all active, stable and closed incidents active_incidents = get_all_by_status(db_session=db_session, status=IncidentStatus.active.value) stable_incidents = get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.stable.value, hours=24 ) closed_incidents = get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.closed.value, hours=24 ) incidents = active_incidents + stable_incidents + closed_incidents # we map incidents to notification filters incidents_notification_filters_mapping = defaultdict(lambda: defaultdict(lambda: [])) notifications = notification_service.get_all_enabled(db_session=db_session) for incident in incidents: for notification in notifications: for search_filter in notification.filters: match = search_service.match( db_session=db_session, filter_spec=search_filter.expression, class_instance=incident, ) if match: incidents_notification_filters_mapping[notification.id][ search_filter.id ].append(incident) if not notification.filters: incidents_notification_filters_mapping[notification.id][0].append(incident) # we create and send an incidents daily report for each notification filter for notification_id, search_filter_dict in incidents_notification_filters_mapping.items(): for search_filter_id, incidents in search_filter_dict.items(): items_grouped = [] items_grouped_template = INCIDENT for idx, incident in enumerate(incidents): try: item = { "commander_fullname": incident.commander.individual.name, "commander_team": incident.commander.team, "commander_weblink": incident.commander.individual.weblink, "incident_id": incident.id, "name": incident.name, "priority": incident.incident_priority.name, "priority_description": incident.incident_priority.description, "status": incident.status, "ticket_weblink": resolve_attr(incident, "ticket.weblink"), "title": incident.title, "type": incident.incident_type.name, "type_description": incident.incident_type.description, } if incident.status != IncidentStatus.closed.value: item.update( { "button_text": "Join Incident", "button_value": str(incident.id), "button_action": f"{ConversationButtonActions.invite_user.value}-{incident.status}-{idx}", } ) items_grouped.append(item) except Exception as e: log.exception(e) notification_kwargs = { "contact_fullname": DISPATCH_HELP_EMAIL, "contact_weblink": DISPATCH_HELP_EMAIL, "items_grouped": items_grouped, "items_grouped_template": items_grouped_template, } notification_params = { "text": INCIDENT_DAILY_REPORT_TITLE, "type": MessageType.incident_daily_report, "template": INCIDENT_DAILY_REPORT, "kwargs": notification_kwargs, } notification = notification_service.get( db_session=db_session, notification_id=notification_id ) notification_service.send( db_session=db_session, notification=notification, notification_params=notification_params, )
def incident_stable_status_flow(incident: Incident, db_session=None): """Runs the incident stable flow.""" # we set the stable time incident.stable_at = datetime.utcnow() # set time immediately db_session.add(incident) db_session.commit() if incident.incident_review_document: log.debug("Incident review document already created... skipping creation.") return storage_plugin = plugin_service.get_active(db_session=db_session, plugin_type="storage") if not storage_plugin: log.warning("Incident review document not created, no storage plugin enabled.") return # we create a copy of the incident review document template and we move it to the incident storage incident_review_document_name = f"{incident.name} - Post Incident Review Document" template = document_service.get_incident_review_template(db_session=db_session) # incident review document is optional if not template: log.warning("No incident review template specificed.") return incident_review_document = storage_plugin.instance.copy_file( folder_id=incident.storage.resource_id, file_id=template.resource_id, name=incident_review_document_name, ) incident_review_document.update( { "name": incident_review_document_name, "resource_type": INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, } ) storage_plugin.instance.move_file( new_folder_id=incident.storage.resource_id, file_id=incident_review_document["id"], ) event_service.log( db_session=db_session, source=storage_plugin.title, description="Incident review document added to storage", incident_id=incident.id, ) document_in = DocumentCreate( name=incident_review_document["name"], resource_id=incident_review_document["id"], resource_type=incident_review_document["resource_type"], weblink=incident_review_document["weblink"], ) incident.documents.append( document_service.create(db_session=db_session, document_in=document_in) ) event_service.log( db_session=db_session, source="Dispatch Core App", description="Incident review document added to incident", incident_id=incident.id, ) # we update the incident review document document_plugin = plugin_service.get_active(db_session=db_session, plugin_type="document") if document_plugin: document_plugin.instance.update( incident.incident_review_document.resource_id, name=incident.name, priority=incident.incident_priority.name, status=incident.status, type=incident.incident_type.name, title=incident.title, description=incident.description, commander_fullname=incident.commander.name, conversation_weblink=resolve_attr(incident, "conversation.weblink"), document_weblink=resolve_attr(incident, "incident_document.weblink"), storage_weblink=resolve_attr(incident, "storage.weblink"), ticket_weblink=resolve_attr(incident, "ticket.weblink"), conference_weblink=resolve_attr(incident, "conference.weblink"), conference_challenge=resolve_attr(incident, "conference.challendge"), ) else: log.warning("No document plugin enabled, could not update template.") # we send a notification about the incident review document to the conversation send_incident_review_document_notification( incident.conversation.channel_id, incident.incident_review_document.weblink, db_session, ) db_session.add(incident) db_session.commit()
def incident_create_flow(*, incident_id: int, checkpoint: str = None, db_session=None): """Creates all resources required for new incidents.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) # create the incident ticket ticket = create_incident_ticket(incident, db_session) if ticket: incident.ticket = ticket_service.create( db_session=db_session, ticket_in=TicketCreate(**ticket) ) # we set the incident name incident.name = ticket["resource_id"] # get the incident participants based on incident type and priority individual_participants, team_participants = get_incident_participants(incident, db_session) # add individuals to incident for individual in individual_participants: participant_flows.add_participant( user_email=individual.email, incident_id=incident.id, db_session=db_session ) event_service.log( db_session=db_session, source="Dispatch Core App", description="Incident participants added to incident", incident_id=incident.id, ) # we create the participant groups (tactical and notification) individual_participants = [x.individual for x in incident.participants] participant_emails = [x.individual.email for x in incident.participants] group_plugin = plugin_service.get_active(db_session=db_session, plugin_type="participant-group") tactical_group = None notification_group = None if group_plugin: try: tactical_group, notification_group = create_participant_groups( incident, individual_participants, team_participants, db_session ) for g in [tactical_group, notification_group]: group_in = GroupCreate( name=g["name"], email=g["email"], resource_type=g["resource_type"], resource_id=g["resource_id"], weblink=g["weblink"], ) incident.groups.append( group_service.create(db_session=db_session, group_in=group_in) ) event_service.log( db_session=db_session, source="Dispatch Core App", description="Tactical and notification groups added to incident", incident_id=incident.id, ) except Exception as e: event_service.log( db_session=db_session, source="Dispatch Core App", description=f"Creation of tactical and notification groups failed. Reason: {e}", incident_id=incident.id, ) log.exception(e) storage_plugin = plugin_service.get_active(db_session=db_session, plugin_type="storage") if storage_plugin: # we create storage resource if group_plugin: storage = create_incident_storage( incident, [tactical_group["email"], notification_group["email"]], db_session ) else: # we don't have a group so add participants directly storage = create_incident_storage(incident, participant_emails, db_session) incident.storage = storage_service.create( db_session=db_session, resource_id=storage["resource_id"], resource_type=storage["resource_type"], weblink=storage["weblink"], ) event_service.log( db_session=db_session, source="Dispatch Core App", description="Storage added to incident", incident_id=incident.id, ) # we create collaboration documents, don't fail the whole flow if this fails try: collab_documents = create_collaboration_documents(incident, db_session) for d in collab_documents: document_in = DocumentCreate( name=d["name"], resource_id=d["resource_id"], resource_type=d["resource_type"], weblink=d["weblink"], ) incident.documents.append( document_service.create(db_session=db_session, document_in=document_in) ) event_service.log( db_session=db_session, source="Dispatch Core App", description="Documents added to incident", incident_id=incident.id, ) except Exception as e: event_service.log( db_session=db_session, source="Dispatch Core App", description=f"Creation of incident documents failed. Reason: {e}", incident_id=incident.id, ) log.exception(e) conference_plugin = plugin_service.get_active(db_session=db_session, plugin_type="conference") if conference_plugin: try: participants = participant_emails if group_plugin: # we use the tactical group email if the group plugin is enabled participants = [tactical_group["email"]] conference = create_conference(incident, participants, db_session) conference_in = ConferenceCreate( resource_id=conference["resource_id"], resource_type=conference["resource_type"], weblink=conference["weblink"], conference_id=conference["id"], conference_challenge=conference["challenge"], ) incident.conference = conference_service.create( db_session=db_session, conference_in=conference_in ) event_service.log( db_session=db_session, source="Dispatch Core App", description="Conference added to incident", incident_id=incident.id, ) except Exception as e: event_service.log( db_session=db_session, source="Dispatch Core App", description=f"Creation of incident conference failed. Reason: {e}", incident_id=incident.id, ) log.exception(e) # we create the conversation for real-time communications conversation_plugin = plugin_service.get_active( db_session=db_session, plugin_type="conversation" ) if conversation_plugin: try: conversation = create_conversation(incident, participant_emails, db_session) conversation_in = ConversationCreate( resource_id=conversation["resource_id"], resource_type=conversation["resource_type"], weblink=conversation["weblink"], channel_id=conversation["id"], ) incident.conversation = conversation_service.create( db_session=db_session, conversation_in=conversation_in ) event_service.log( db_session=db_session, source="Dispatch Core App", description="Conversation added to incident", incident_id=incident.id, ) # we set the conversation topic set_conversation_topic(incident, db_session) except Exception as e: event_service.log( db_session=db_session, source="Dispatch Core App", description=f"Creation of incident conversation failed. Reason: {e}", incident_id=incident.id, ) log.exception(e) db_session.add(incident) db_session.commit() # we update the incident ticket update_external_incident_ticket(incident, db_session) # we update the investigation document document_plugin = plugin_service.get_active(db_session=db_session, plugin_type="document") if document_plugin: if incident.incident_document: try: document_plugin.instance.update( incident.incident_document.resource_id, name=incident.name, priority=incident.incident_priority.name, status=incident.status, type=incident.incident_type.name, title=incident.title, description=incident.description, commander_fullname=incident.commander.name, conversation_weblink=resolve_attr(incident, "conversation.weblink"), document_weblink=resolve_attr(incident, "incident_document.weblink"), storage_weblink=resolve_attr(incident, "storage.weblink"), ticket_weblink=resolve_attr(incident, "ticket.weblink"), conference_weblink=resolve_attr(incident, "conference.weblink"), conference_challenge=resolve_attr(incident, "conference.challendge"), ) except Exception as e: event_service.log( db_session=db_session, source="Dispatch Core App", description=f"Incident documents rendering failed. Reason: {e}", incident_id=incident.id, ) log.exception(e) if incident.visibility == Visibility.open: send_incident_notifications(incident, db_session) event_service.log( db_session=db_session, source="Dispatch Core App", description="Incident notifications sent", incident_id=incident.id, ) suggested_document_items = get_suggested_document_items(incident.id, db_session) for participant in incident.participants: # we announce the participant in the conversation # should protect ourselves from failures of any one participant try: send_incident_participant_announcement_message( participant.individual.email, incident.id, db_session ) # we send the welcome messages to the participant send_incident_welcome_participant_messages( participant.individual.email, incident.id, db_session ) send_incident_suggested_reading_messages( incident.id, suggested_document_items, participant.individual.email, db_session ) except Exception as e: log.exception(e) event_service.log( db_session=db_session, source="Dispatch Core App", description="Participants announced and welcome messages sent", incident_id=incident.id, )
def daily_summary(db_session=None): """Fetches all open incidents and provides a daily summary.""" blocks = [] blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": f"*{INCIDENT_DAILY_SUMMARY_DESCRIPTION}*" }, }) active_incidents = get_all_by_status(db_session=db_session, status=IncidentStatus.active) if active_incidents: blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": f"*{INCIDENT_DAILY_SUMMARY_ACTIVE_INCIDENTS_DESCRIPTION}*", }, }) for idx, incident in enumerate(active_incidents): ticket_weblink = resolve_attr(incident, "ticket.weblink") if incident.visibility == Visibility.open: try: blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": (f"*<{ticket_weblink}|{incident.name}>*\n" f"*Title*: {incident.title}\n" f"*Type*: {incident.incident_type.name}\n" f"*Priority*: {incident.incident_priority.name}\n" f"*Incident Commander*: <{incident.commander.weblink}|{incident.commander.name}>" ), }, "block_id": f"{ConversationButtonActions.invite_user}-active-{idx}", "accessory": { "type": "button", "text": { "type": "plain_text", "text": "Join Incident" }, "value": f"{incident.id}", }, }) except Exception as e: log.exception(e) else: blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": INCIDENT_DAILY_SUMMARY_NO_ACTIVE_INCIDENTS_DESCRIPTION, }, }) blocks.append({"type": "divider"}) blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": f"*{INCIDENT_DAILY_SUMMARY_STABLE_CLOSED_INCIDENTS_DESCRIPTION}*", }, }) hours = 24 stable_incidents = get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.stable, hours=hours) closed_incidents = get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.closed, hours=hours) if stable_incidents or closed_incidents: for idx, incident in enumerate(stable_incidents): ticket_weblink = resolve_attr(incident, "ticket.weblink") if incident.visibility == Visibility.open: try: blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": (f"*<{ticket_weblink}|{incident.name}>*\n" f"*Title*: {incident.title}\n" f"*Type*: {incident.incident_type.name}\n" f"*Priority*: {incident.incident_priority.name}\n" f"*Incident Commander*: <{incident.commander.weblink}|{incident.commander.name}>\n" f"*Status*: {incident.status}"), }, "block_id": f"{ConversationButtonActions.invite_user}-stable-{idx}", "accessory": { "type": "button", "text": { "type": "plain_text", "text": "Join Incident" }, "value": f"{incident.id}", }, }) except Exception as e: log.exception(e) for incident in closed_incidents: ticket_weblink = resolve_attr(incident, "ticket.weblink") if incident.visibility == Visibility.open: try: blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": (f"*<{ticket_weblink}|{incident.name}>*\n" f"*Title*: {incident.title}\n" f"*Type*: {incident.incident_type.name}\n" f"*Priority*: {incident.incident_priority.name}\n" f"*Incident Commander*: <{incident.commander.weblink}|{incident.commander.name}>\n" f"*Status*: {incident.status}"), }, }) except Exception as e: log.exception(e) else: blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": INCIDENT_DAILY_SUMMARY_NO_STABLE_CLOSED_INCIDENTS_DESCRIPTION, }, }) # NOTE INCIDENT_ONCALL_SERVICE_ID is optional if INCIDENT_ONCALL_SERVICE_ID: oncall_service = service_service.get_by_external_id( db_session=db_session, external_id=INCIDENT_ONCALL_SERVICE_ID) oncall_plugin = plugins.get(oncall_service.type) oncall_email = oncall_plugin.get(service_id=INCIDENT_ONCALL_SERVICE_ID) oncall_individual = individual_service.resolve_user_by_email( oncall_email, db_session) blocks.append({ "type": "context", "elements": [{ "type": "mrkdwn", "text": f"For any questions about this notification, please reach out to <{oncall_individual['weblink']}|{oncall_individual['fullname']}> (current on-call)", }], }) plugin = plugin_service.get_active(db_session=db_session, plugin_type="conversation") for c in INCIDENT_NOTIFICATION_CONVERSATIONS: plugin.instance.send(c, "Incident Daily Summary", {}, "", blocks=blocks)
def daily_report(db_session=None): """ Creates and sends an incident daily report. """ active_incidents = get_all_by_status(db_session=db_session, status=IncidentStatus.active) stable_incidents = get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.stable, hours=24) closed_incidents = get_all_last_x_hours_by_status( db_session=db_session, status=IncidentStatus.closed, hours=24) incidents = active_incidents + stable_incidents + closed_incidents items_grouped_template = INCIDENT items_grouped = [] for idx, incident in enumerate(incidents): if incident.visibility == Visibility.open: try: item = { "commander_fullname": incident.commander.individual.name, "commander_weblink": incident.commander.individual.weblink, "incident_id": incident.id, "name": incident.name, "priority": incident.incident_priority.name, "priority_description": incident.incident_priority.description, "status": incident.status, "ticket_weblink": resolve_attr(incident, "ticket.weblink"), "title": incident.title, "type": incident.incident_type.name, "type_description": incident.incident_type.description, } if incident.status != IncidentStatus.closed.value: item.update({ "button_text": "Join Incident", "button_value": str(incident.id), "button_action": f"{ConversationButtonActions.invite_user.value}-{incident.status}-{idx}", }) items_grouped.append(item) except Exception as e: log.exception(e) notification_kwargs = { "items_grouped": items_grouped, "items_grouped_template": items_grouped_template, } notification_params = { "text": INCIDENT_DAILY_REPORT_TITLE, "type": MessageType.incident_daily_report, "template": INCIDENT_DAILY_REPORT, "kwargs": notification_kwargs, } notification_service.filter_and_send( db_session=db_session, class_instance=incident, notification_params=notification_params)