def send_welcome_email_to_participant(participant_email: str, incident: Incident, db_session: SessionLocal): """Sends a welcome email to the participant.""" # we get the incident documents incident_document = get_document( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) incident_faq = get_document( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_FAQ_DOCUMENT, ) email_plugin = plugins.get(INCIDENT_PLUGIN_EMAIL_SLUG) email_plugin.send( participant_email, INCIDENT_PARTICIPANT_WELCOME_MESSAGE, MessageType.incident_participant_welcome, name=incident.name, title=incident.title, status=incident.status, priority=incident.incident_priority.name, commander_fullname=incident.commander.name, commander_weblink=incident.commander.weblink, document_weblink=incident_document.weblink, storage_weblink=incident.storage.weblink, ticket_weblink=incident.ticket.weblink, faq_weblink=incident_faq.weblink, ) log.debug(f"Welcome email sent to {participant_email}.")
def send_incident_resources_ephemeral_message_to_participant( user_id: str, incident: Incident, db_session: SessionLocal): """Sends the list of incident resources to the participant via an ephemeral message.""" # we get the incident documents incident_document = get_document( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) incident_faq = get_document( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_FAQ_DOCUMENT, ) # we send the ephemeral message convo_plugin = plugins.get(INCIDENT_PLUGIN_CONVERSATION_SLUG) convo_plugin.send_ephemeral( incident.conversation.channel_id, user_id, "Incident Resources Message", INCIDENT_RESOURCES_MESSAGE, MessageType.incident_resources_message, commander_fullname=incident.commander.name, commander_weblink=incident.commander.weblink, document_weblink=incident_document.weblink, storage_weblink=incident.storage.weblink, faq_weblink=incident_faq.weblink, ) log.debug( f"List of incident resources sent to {user_id} via ephemeral message.")
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 # we get the incident documents incident_document = get_document( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) incident_faq = get_document(db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_FAQ_DOCUMENT) # we send status notifications to conversations convo_plugin = plugins.get(INCIDENT_PLUGIN_CONVERSATION_SLUG) for conversation in INCIDENT_NOTIFICATION_CONVERSATIONS: convo_plugin.send( conversation, notification_text, message_template, notification_type, name=incident.name, title=incident.title, status=incident.status, priority=incident.incident_priority.name, commander_fullname=incident.commander.name, commander_weblink=incident.commander.weblink, document_weblink=incident_document.weblink, storage_weblink=incident.storage.weblink, ticket_weblink=incident.ticket.weblink, faq_weblink=incident_faq.weblink, incident_id=incident.id, ) # we send status notifications to distribution lists email_plugin = plugins.get(INCIDENT_PLUGIN_EMAIL_SLUG) for distro in INCIDENT_NOTIFICATION_DISTRIBUTION_LISTS: email_plugin.send( distro, message_template, notification_type, name=incident.name, title=incident.title, status=incident.status, priority=incident.incident_priority.name, commander_fullname=incident.commander.name, commander_weblink=incident.commander.weblink, document_weblink=incident_document.weblink, storage_weblink=incident.storage.weblink, ticket_weblink=incident.ticket.weblink, faq_weblink=incident_faq.weblink, incident_id=incident.id, ) log.debug(f"Incident status notifications sent.")
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 incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we get the incident documents incident_document = get_document( db_session=db_session, incident_id=incident_id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) incident_faq = get_document(db_session=db_session, incident_id=incident_id, resource_type=INCIDENT_RESOURCE_FAQ_DOCUMENT) incident_conversation_commands_reference_document = get_document( db_session=db_session, incident_id=incident_id, resource_type= INCIDENT_RESOURCE_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT, ) # we send the ephemeral message convo_plugin = plugins.get(INCIDENT_PLUGIN_CONVERSATION_SLUG) convo_plugin.send_ephemeral( incident.conversation.channel_id, participant_email, "Incident Welcome Message", INCIDENT_PARTICIPANT_WELCOME_MESSAGE, MessageType.incident_participant_welcome, 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=incident_document.weblink, storage_weblink=incident.storage.weblink, ticket_weblink=incident.ticket.weblink, faq_weblink=incident_faq.weblink, conference_weblink=incident.conference.weblink, conference_challenge=incident.conference.conference_challenge, conversation_commands_reference_document_weblink= incident_conversation_commands_reference_document.weblink, ) log.debug(f"Welcome ephemeral message sent to {participant_email}.")
def sync_tasks(db_session, incidents, notify: bool = False): """Syncs tasks and sends update notifications to incident channels.""" drive_task_plugin = plugins.get(INCIDENT_PLUGIN_TASK_SLUG) for incident in incidents: for doc_type in [ INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, ]: try: # we get the document object document = get_document(db_session=db_session, incident_id=incident.id, resource_type=doc_type) if not document: # the document may have not been created yet (e.g. incident review document) break # we get the list of tasks in the document tasks = drive_task_plugin.list(file_id=document.resource_id) for task in tasks: # we get the task information try: create_or_update_task(db_session, incident, task["task"], notify=notify) except Exception as e: log.exception(e) sentry_sdk.capture_exception(e) except Exception as e: log.exception(e) sentry_sdk.capture_exception(e)
def sync_tasks(db_session, incidents, notify: bool = False): """Syncs tasks and sends update notifications to incident channels.""" for incident in incidents: drive_task_plugin = plugin_service.get_active_instance( db_session=db_session, project_id=incident.project.id, plugin_type="task") if not drive_task_plugin: log.warning( f"Skipping task sync no task plugin enabled. IncidentId: {incident.id}" ) continue for doc_type in [ INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, ]: try: # we get the document object document = get_document(db_session=db_session, incident_id=incident.id, resource_type=doc_type) if not document: # the document may have not been created yet (e.g. incident review document) break # we get the list of tasks in the document tasks = drive_task_plugin.instance.list( file_id=document.resource_id) for task in tasks: # we get the task information try: create_or_update_task(db_session, incident, task["task"], notify=notify, sync_external=False) except Exception as e: log.exception(e) except Exception as e: log.exception(e)
def incident_update_flow(user_email: str, incident_id: int, previous_incident: IncidentRead, notify=True, db_session=None): """Runs the incident update flow.""" conversation_topic_change = False # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) if previous_incident.incident_type.name != incident.incident_type.name: conversation_topic_change = True if previous_incident.incident_priority.name != incident.incident_priority.name: conversation_topic_change = True if previous_incident.status.value != incident.status: conversation_topic_change = True if conversation_topic_change: # we update the conversation topic set_conversation_topic(incident) if notify: send_incident_update_notifications(incident, previous_incident) # we get the incident document incident_document = get_document( db_session=db_session, incident_id=incident_id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) # we update the external ticket update_incident_ticket( incident.ticket.resource_id, title=incident.title, description=incident.description, incident_type=incident.incident_type.name, priority=incident.incident_priority.name, commander_email=incident.commander.email, conversation_weblink=incident.conversation.weblink, conference_weblink=incident.conference.weblink, document_weblink=incident_document.weblink, storage_weblink=incident.storage.weblink, visibility=incident.visibility, ) log.debug(f"Updated the external ticket {incident.ticket.resource_id}.") # get the incident participants based on incident type and priority individual_participants, team_participants = get_incident_participants( incident, db_session) # lets not attempt to add new participants for non-active incidents (it's confusing) if incident.status == IncidentStatus.active: # we add the individuals as incident participants for individual in individual_participants: incident_add_or_reactivate_participant_flow(individual.email, incident.id, db_session=db_session) # we get the notification group notification_group = group_service.get_by_incident_id_and_resource_type( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_NOTIFICATIONS_GROUP, ) team_participant_emails = [x.email for x in team_participants] # we add the team distributions lists to the notifications group group_plugin = plugins.get(INCIDENT_PLUGIN_GROUP_SLUG) group_plugin.add(notification_group.email, team_participant_emails) if previous_incident.status.value != incident.status: if incident.status == IncidentStatus.active: incident_active_flow(incident_id=incident.id, db_session=db_session) elif incident.status == IncidentStatus.stable: incident_stable_flow(incident_id=incident.id, db_session=db_session) elif incident.status == IncidentStatus.closed: if previous_incident.status.value == IncidentStatus.active: incident_stable_flow(incident_id=incident.id, db_session=db_session) incident_closed_flow(incident_id=incident.id, db_session=db_session)
def incident_stable_flow(incident_id: int, command: Optional[dict] = None, db_session=None): """Runs the incident stable flow.""" # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we set the stable time incident.stable_at = datetime.utcnow() # we remind the incident commander to write a status report send_incident_status_report_reminder(incident) # we update the incident cost incident_cost = incident_service.calculate_cost(incident_id, db_session) # we update the external ticket update_incident_ticket(incident.ticket.resource_id, status=IncidentStatus.stable.lower(), cost=incident_cost) incident_review_document = get_document( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, ) if not incident_review_document: storage_plugin = plugins.get(INCIDENT_PLUGIN_STORAGE_SLUG) # 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" incident_review_document = storage_plugin.copy_file( team_drive_id=incident.storage.resource_id, file_id=INCIDENT_STORAGE_INCIDENT_REVIEW_FILE_ID, name=incident_review_document_name, ) incident_review_document.update({ "name": incident_review_document_name, "resource_type": INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, }) storage_plugin.move_file( new_team_drive_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 get the incident investigation and faq documents incident_document = get_document( db_session=db_session, incident_id=incident_id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) # we update the incident review document update_document( incident_review_document["id"], incident.name, incident.incident_priority.name, incident.status, incident.incident_type.name, incident.title, incident.description, incident.commander.name, incident.conversation.weblink, incident_document.weblink, incident.storage.weblink, incident.ticket.weblink, ) # we send a notification about the incident review document to the conversation send_incident_review_document_notification( incident.conversation.channel_id, incident_review_document["weblink"]) db_session.add(incident) db_session.commit() event_service.log( db_session=db_session, source="Dispatch Core App", description=f"Incident marked as {incident.status}", incident_id=incident.id, )
def incident_assign_role_flow(assigner_email: str, incident_id: int, assignee_email: str, assignee_role: str, db_session=None): """Runs the incident participant role assignment flow.""" # we resolve the assigner and assignee's contact information contact_plugin = plugins.get(INCIDENT_PLUGIN_CONTACT_SLUG) assigner_contact_info = contact_plugin.get(assigner_email) assignee_contact_info = contact_plugin.get(assignee_email) # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we get the participant object for the assignee assignee_participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident.id, email=assignee_contact_info["email"]) if not assignee_participant: # The assignee is not a participant. We add them to the incident incident_add_or_reactivate_participant_flow(assignee_email, incident.id, db_session=db_session) # we run the participant assign role flow result = participant_role_flows.assign_role_flow(incident.id, assignee_contact_info, assignee_role, db_session) if result == "assignee_has_role": # NOTE: This is disabled until we can determine the source of the caller # we let the assigner know that the assignee already has this role # send_incident_participant_has_role_ephemeral_message( # assigner_email, assignee_contact_info, assignee_role, incident # ) return if result == "role_not_assigned": # NOTE: This is disabled until we can determine the source of the caller # we let the assigner know that we were not able to assign the role # send_incident_participant_role_not_assigned_ephemeral_message( # assigner_email, assignee_contact_info, assignee_role, incident # ) return if assignee_role != ParticipantRoleType.participant: # we send a notification to the incident conversation send_incident_new_role_assigned_notification(assigner_contact_info, assignee_contact_info, assignee_role, incident) if assignee_role == ParticipantRoleType.incident_commander: # we update the conversation topic set_conversation_topic(incident) # we get the incident document incident_document = get_document( db_session=db_session, incident_id=incident_id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) # we update the external ticket update_incident_ticket( incident.ticket.resource_id, description=incident.description, incident_type=incident.incident_type.name, commander_email=incident.commander.email, conversation_weblink=incident.conversation.weblink, document_weblink=incident_document.weblink, storage_weblink=incident.storage.weblink, visibility=incident.visibility, conference_weblink=incident.conference.weblink, )
def incident_edit_flow(user_email: str, incident_id: int, action: dict, db_session=None): """Runs the incident edit flow.""" notify = action["submission"]["notify"] incident_title = action["submission"]["title"] incident_description = action["submission"]["description"] incident_type = action["submission"]["type"] incident_priority = action["submission"]["priority"] incident_visibility = action["submission"]["visibility"] conversation_topic_change = False # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we update the incident title incident.title = incident_title log.debug(f"Updated the incident title to {incident_title}.") # we update the incident description incident.description = incident_description log.debug(f"Updated the incident description to {incident_description}.") if incident_type != incident.incident_type.name: # we update the incident type incident_type_obj = incident_type_service.get_by_name( db_session=db_session, name=incident_type) incident.incident_type_id = incident_type_obj.id log.debug(f"Updated the incident type to {incident_type}.") conversation_topic_change = True if incident_priority != incident.incident_priority.name: # we update the incident priority incident_priority_obj = incident_priority_service.get_by_name( db_session=db_session, name=incident_priority) incident.incident_priority_id = incident_priority_obj.id log.debug(f"Updated the incident priority to {incident_priority}.") conversation_topic_change = True if incident_visibility != incident.visibility: # we update the incident visibility incident.visibility = incident_visibility log.debug(f"Updated the incident visibility to {incident_visibility}.") if notify == "Yes": send_incident_change_notifications(incident, incident_title, incident_type, incident_priority) # we commit the changes to the incident db_session.add(incident) db_session.commit() if conversation_topic_change: # we update the conversation topic set_conversation_topic(incident) # we get the incident document incident_document = get_document( db_session=db_session, incident_id=incident_id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) # we update the external ticket update_incident_ticket( incident.ticket.resource_id, title=incident.title, description=incident.description, incident_type=incident_type, priority=incident_priority, commander_email=incident.commander.email, conversation_weblink=incident.conversation.weblink, document_weblink=incident_document.weblink, storage_weblink=incident.storage.weblink, ) log.debug(f"Updated the external ticket {incident.ticket.resource_id}.") # get the incident participants based on incident type and priority individual_participants, team_participants = get_incident_participants( db_session, incident.incident_type, incident.incident_priority, incident.description) # we add the individuals as incident participants for individual in individual_participants: incident_add_or_reactivate_participant_flow(individual.email, incident.id, db_session=db_session) # we get the tactical group notification_group = get_group( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_NOTIFICATIONS_GROUP, ) team_participant_emails = [x.email for x in team_participants] # we add the team distributions lists to the notifications group group_plugin = plugins.get(INCIDENT_PLUGIN_GROUP_SLUG) group_plugin.add(notification_group.email, team_participant_emails) log.debug(f"Resolved and added new participants to the incident.")
def incident_stable_flow(incident_id: int, command: Optional[dict] = None, db_session=None): """Runs the incident stable flow.""" # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) if incident.status == IncidentStatus.stable: if command: convo_plugin = plugins.get(INCIDENT_PLUGIN_CONVERSATION_SLUG) convo_plugin.send_ephemeral( command["channel_id"], command["user_id"], "Incident Already Stable Notification", blocks=[{ "type": "section", "text": { "type": "plain_text", "text": "The incident is already stable. Aborting command...", }, }], ) return # we update the status of the incident update_incident_status(db_session=db_session, incident=incident, status=IncidentStatus.stable) log.debug( f"We have updated the status of the incident to {IncidentStatus.stable}." ) # we update the incident cost incident_cost = incident_service.calculate_cost(incident_id, db_session) log.debug(f"We have updated the cost of the incident.") # we update the external ticket update_incident_ticket( incident.ticket.resource_id, incident_type=incident.incident_type.name, status=IncidentStatus.stable.lower(), cost=incident_cost, ) log.debug( f"We have updated the status of the external ticket to {IncidentStatus.stable}." ) # we update the conversation topic set_conversation_topic(incident) incident_review_document = get_document( db_session=db_session, incident_id=incident.id, resource_type=INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, ) if not incident_review_document: storage_plugin = plugins.get(INCIDENT_PLUGIN_STORAGE_SLUG) # 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" incident_review_document = storage_plugin.copy_file( team_drive_id=incident.storage.resource_id, file_id=INCIDENT_STORAGE_INCIDENT_REVIEW_FILE_ID, name=incident_review_document_name, ) incident_review_document.update({ "name": incident_review_document_name, "resource_type": INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, }) storage_plugin.move_file( new_team_drive_id=incident.storage.resource_id, file_id=incident_review_document["id"]) log.debug( "We have added the incident review document in the incident storage." ) 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)) db_session.add(incident) db_session.commit() log.debug( "We have added the incident review document to the incident.") # we get the incident investigation and faq documents incident_document = get_document( db_session=db_session, incident_id=incident_id, resource_type=INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, ) # we update the incident review document update_document( incident_review_document["id"], incident.name, incident.incident_priority.name, incident.status, incident.title, incident.description, incident.commander.name, incident.conversation.weblink, incident_document.weblink, incident.storage.weblink, incident.ticket.weblink, ) log.debug("We have updated the incident review document.") # we send a notification about the incident review document to the conversation send_incident_review_document_notification( incident.conversation.channel_id, incident_review_document["weblink"]) log.debug( "We have sent a notification about the incident review document to the conversation." ) # we send the stable notifications send_incident_status_notifications(incident, db_session) log.debug("We have sent the incident stable notifications.")
def sync_tasks(db_session=None): """Syncs incident tasks.""" # we get all active and stable incidents active_incidents = incident_service.get_all_by_status( db_session=db_session, status=IncidentStatus.active) stable_incidents = incident_service.get_all_by_status( db_session=db_session, status=IncidentStatus.stable) incidents = active_incidents + stable_incidents # we create an instance of the drive task plugin drive_task_plugin = plugins.get(INCIDENT_PLUGIN_TASK_SLUG) for incident in incidents: for doc_type in [ INCIDENT_RESOURCE_INVESTIGATION_DOCUMENT, INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, ]: # we get the document object document = get_document(db_session=db_session, incident_id=incident.id, resource_type=doc_type) if not document: # the document may have not been created yet (e.g. incident review document) break # we get the list of tasks in the document tasks = drive_task_plugin.list(file_id=document.resource_id) for t in tasks: # we get the task information creator = t["task"]["owner"] assignees = ", ".join(t["task"]["assignees"]) description = t["task"]["description"][0] status = TaskStatus.open if not t["task"][ "status"] else TaskStatus.resolved resource_id = t["task"]["id"] weblink = t["task"]["web_link"] incident_task = task_service.get_by_resource_id( db_session=db_session, resource_id=t["task"]["id"]) if incident_task: if status == TaskStatus.open: # we don't need to take any actions if the status of the task in the collaboration doc is open break else: if incident_task.status == TaskStatus.resolved: # we don't need to take any actions if the task has already been marked as resolved in the database break else: # we mark the task as resolved in the database incident_task.status = TaskStatus.resolved db_session.add(incident_task) db_session.commit() # we send a notification to the incident conversation notification_text = "Incident Notification" notification_type = "incident-notification" convo_plugin = plugins.get( INCIDENT_PLUGIN_CONVERSATION_SLUG) convo_plugin.send( incident.conversation.channel_id, notification_text, INCIDENT_TASK_RESOLVED_NOTIFICATION, notification_type, task_assignees=assignees, task_description=description, task_weblink=weblink, ) else: # we add the task to the incident task = task_service.create( db_session=db_session, creator=creator, assignees=assignees, description=description, status=status, resource_id=resource_id, resource_type=INCIDENT_RESOURCE_INCIDENT_TASK, weblink=weblink, ) incident.tasks.append(task) db_session.add(incident) db_session.commit() # we send a notification to the incident conversation notification_text = "Incident Notification" notification_type = "incident-notification" convo_plugin = plugins.get( INCIDENT_PLUGIN_CONVERSATION_SLUG) convo_plugin.send( incident.conversation.channel_id, notification_text, INCIDENT_TASK_NEW_NOTIFICATION, notification_type, task_assignees=assignees, task_description=description, task_weblink=weblink, )