def incident_add_or_reactivate_participant_flow( user_email: str, incident_id: int, role: ParticipantRoleType = None, event: dict = None, db_session=None, ) -> Participant: """Runs the add or reactivate incident participant flow.""" participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) if participant: if participant.is_active: log.debug(f"{user_email} is already an active participant.") else: # we reactivate the participant reactivated = participant_flows.reactivate_participant( user_email, incident_id, db_session) if reactivated: # we add the participant to the conversation add_participant_to_conversation(user_email, incident_id, db_session) # we announce the participant in the conversation send_incident_participant_announcement_message( user_email, incident_id, db_session) # we send the welcome messages to the participant send_incident_welcome_participant_messages( user_email, incident_id, db_session) else: # we add the participant to the incident participant = participant_flows.add_participant(user_email, incident_id, db_session, role=role) # we add the participant to the tactical group add_participant_to_tactical_group(user_email, incident_id, db_session) # we add the participant to the conversation add_participant_to_conversation(user_email, incident_id, db_session) # we announce the participant in the conversation send_incident_participant_announcement_message(user_email, incident_id, db_session) # we send the welcome messages to the participant send_incident_welcome_participant_messages(user_email, incident_id, db_session) # we send a suggested reading message to the participant suggested_document_items = get_suggested_document_items( incident_id, db_session) send_incident_suggested_reading_messages(incident_id, suggested_document_items, user_email, db_session) return participant
def member_joined_channel( user_email: str, incident_id: int, event: EventEnvelope, db_session=None, ): """Handles the member_joined_channel slack event.""" participant = incident_flows.incident_add_or_reactivate_participant_flow( user_email=user_email, incident_id=incident_id, db_session=db_session) if event.event.inviter: # we update the participant's metadata if not dispatch_slack_service.is_user(event.event.inviter): # we default to the incident commander when we don't know how the user was added added_by_participant = participant_service.get_by_incident_id_and_role( db_session=db_session, incident_id=incident_id, role=ParticipantRoleType.incident_commander, ) participant.added_by = added_by_participant participant.added_reason = ( f"Participant added by {added_by_participant.individual.name}") else: inviter_email = get_user_email(client=slack_client, user_id=event.event.inviter) added_by_participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=inviter_email) participant.added_by = added_by_participant participant.added_reason = event.event.text db_session.add(participant) db_session.commit()
def member_joined_channel( user_email: str, incident_id: int, event: EventEnvelope, db_session=None, ): """Handles the member_joined_channel slack event.""" participant = incident_flows.incident_add_or_reactivate_participant_flow( user_email=user_email, incident_id=incident_id, db_session=db_session) # update participant metadata inviter_email = get_user_email(client=slack_client, user_id=event.event.inviter) added_by_participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=inviter_email) # default to IC when we don't know how the user was added if not added_by_participant: participant.added_by = participant_service.get_by_incident_id_and_role( db_session=db_session, incident_id=incident_id, role=ParticipantRoleType.incident_commander, ) participant.added_reason = "User was automatically added by Dispatch." else: participant.added_by = added_by_participant participant.added_reason = event.event.text db_session.commit()
def create_instance(*, db_session, instance_in: WorkflowInstanceCreate) -> WorkflowInstance: """Creates a new workflow instance.""" instance = WorkflowInstance(**instance_in.dict( exclude={"incident", "workflow", "creator", "artifacts"})) incident = incident_service.get(db_session=db_session, incident_id=instance_in.incident.id) instance.incident = incident workflow = get(db_session=db_session, workflow_id=instance_in.workflow.id) instance.workflow = workflow creator = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident.id, email=instance_in.creator.individual.email) instance.creator = creator for a in instance_in.artifacts: artifact_document = document_service.create(db_session=db_session, document_in=a) instance.artifacts.append(artifact_document) db_session.add(instance) db_session.commit() return instance
def rating_feedback_from_submitted_form(action: dict, db_session=None): """Adds rating and feeback to incident based on submitted form data.""" incident_id = action["view"]["private_metadata"]["incident_id"] incident = incident_service.get(db_session=db_session, incident_id=incident_id) user_email = action["user"]["email"] participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) feedback = parsed_form_data.get(IncidentRatingFeedbackBlockFields.feedback) rating = parsed_form_data.get( IncidentRatingFeedbackBlockFields.rating)["value"] anonymous = parsed_form_data.get( IncidentRatingFeedbackBlockFields.anonymous)["value"] feedback_in = FeedbackCreate(rating=rating, feedback=feedback) feedback = feedback_service.create(db_session=db_session, feedback_in=feedback_in) incident.feedback.append(feedback) if anonymous == "": participant.feedback.append(feedback) db_session.add(participant) db_session.add(incident) db_session.commit()
def save_status_report( user_email: str, conditions: str, actions: str, needs: str, incident_id: int, db_session: SessionLocal, ): """Saves a new status report.""" # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we create a new status report status_report = create( db_session=db_session, conditions=conditions, actions=actions, needs=needs ) # we load the participant participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email ) # we save the status report participant.status_reports.append(status_report) incident.status_reports.append(status_report) db_session.add(participant) db_session.add(incident) db_session.commit() log.debug(f"New status report created by {participant.individual.name}")
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 update the external ticket update_external_incident_ticket(incident, db_session)
def create_tactical_report( user_email: str, incident_id: int, tactical_report_in: TacticalReportCreate, organization_slug: str = None, db_session=None, ): """Creates and sends a new tactical report to a conversation.""" conditions = tactical_report_in.conditions actions = tactical_report_in.actions needs = tactical_report_in.needs # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we create a new tactical report details = {"conditions": conditions, "actions": actions, "needs": needs} tactical_report_in = ReportCreate(details=details, type=ReportTypes.tactical_report) tactical_report = create(db_session=db_session, report_in=tactical_report_in) # we load the participant participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) # we save the tactical report participant.reports.append(tactical_report) incident.reports.append(tactical_report) db_session.add(participant) db_session.add(incident) db_session.commit() event_service.log( db_session=db_session, source="Incident Participant", description= f"{participant.individual.name} created a new tactical report", details={ "conditions": conditions, "actions": actions, "needs": needs }, incident_id=incident_id, individual_id=participant.individual.id, ) # we send the tactical report to the conversation send_tactical_report_to_conversation(incident_id, conditions, actions, needs, db_session) # we send the tactical report to the tactical group send_tactical_report_to_tactical_group(incident_id, tactical_report, db_session) return tactical_report
def create_tactical_report( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None ): """Creates and sends a new tactical report to a conversation.""" conditions = action["submission"]["conditions"] actions = action["submission"]["actions"] needs = action["submission"]["needs"] # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we create a new tactical report details = {"conditions": conditions, "actions": actions, "needs": needs} tactical_report_in = ReportCreate(details=details, type=ReportTypes.tactical_report) tactical_report = create(db_session=db_session, report_in=tactical_report_in) # we load the participant participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email ) # we save the tactical report participant.reports.append(tactical_report) incident.reports.append(tactical_report) db_session.add(participant) db_session.add(incident) db_session.commit() event_service.log( db_session=db_session, source="Incident Participant", description=f"{participant.individual.name} created a new tactical report", details={"conditions": conditions, "actions": actions, "needs": needs}, incident_id=incident_id, individual_id=participant.individual.id, ) # we send the tactical report to the conversation send_tactical_report_to_conversation(incident_id, conditions, actions, needs, db_session) # we send the tactical report to the tactical group send_tactical_report_to_tactical_group(incident_id, tactical_report, db_session) # we let the user know that the report has been sent to the tactical group send_feedack_to_user( incident.conversation.channel_id, user_id, f"The tactical report has been emailed to the incident tactical group ({incident.notifications_group.email}).", db_session, ) return tactical_report
def incident_add_or_reactivate_participant_flow( user_email: str, incident_id: int, role: ParticipantRoleType = None, db_session=None ): """Runs the add or reactivate incident participant flow.""" # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) # We get information about the individual contact_plugin = plugins.get(INCIDENT_PLUGIN_CONTACT_SLUG) individual_info = contact_plugin.get(user_email) participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email ) if participant: if participant.is_active: log.debug(f"{individual_info['fullname']} is already an active participant.") return else: # we reactivate the participant reactivated = participant_flows.reactivate_participant( user_email, incident_id, db_session ) if reactivated: # we add the participant to the conversation add_participant_to_conversation(incident.conversation.channel_id, user_email) # we announce the participant in the conversation send_incident_participant_announcement_message(user_email, incident, db_session) # we send the welcome messages to the participant send_incident_welcome_participant_messages(user_email, incident, db_session) return else: # we add the participant to the incident added = participant_flows.add_participant(user_email, incident_id, db_session, role=role) if added: # we add the participant to the tactical group add_participant_to_tactical_group(user_email, incident_id) # we add the participant to the conversation add_participant_to_conversation(incident.conversation.channel_id, user_email) # we announce the participant in the conversation send_incident_participant_announcement_message(user_email, incident, db_session) # we send the welcome messages to the participant send_incident_welcome_participant_messages(user_email, incident, db_session)
def test_get_by_incident_id_and_email(session, incident, participant, individual_contact): from dispatch.participant.service import get_by_incident_id_and_email individual_contact.participant.append(participant) incident.participants.append(participant) t_participant = get_by_incident_id_and_email( db_session=session, incident_id=incident.id, email=individual_contact.email) assert t_participant.incident_id == incident.id assert t_participant.individual.email == individual_contact.email
def after_hours( config: SlackConversationConfiguration, user_id: str, user_email: str, channel_id: str, incident_id: int, event: EventEnvelope = None, db_session=None, slack_client=None, ): """Notifies the user that this incident is current in after hours mode.""" # we ignore user channel and group join messages if event.event.subtype in ["channel_join", "group_join"]: return incident = incident_service.get(db_session=db_session, incident_id=incident_id) # get their timezone from slack commander_info = dispatch_slack_service.get_user_info_by_email( slack_client, email=incident.commander.individual.email) commander_tz = commander_info["tz"] if not is_business_hours(commander_tz): # send ephermal message blocks = [{ "type": "section", "text": { "type": "mrkdwn", "text": ((f"Responses may be delayed. The current incident priority is *{incident.incident_priority.name}*" f" and your message was sent outside of the Incident Commander's working hours (Weekdays, 9am-5pm, {commander_tz} timezone)." )), }, }] participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) if not participant.after_hours_notification: dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, "", blocks=blocks) participant.after_hours_notification = True db_session.add(participant) db_session.commit()
def add_timeline_event_from_submitted_form( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, config: SlackConversationConfiguration = None, db_session=None, slack_client=None, ): """Adds event to incident timeline based on submitted form data.""" submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) event_date = parsed_form_data.get(AddTimelineEventBlockId.date) event_hour = parsed_form_data.get(AddTimelineEventBlockId.hour)["value"] event_minute = parsed_form_data.get(AddTimelineEventBlockId.minute)["value"] event_timezone_selection = parsed_form_data.get(AddTimelineEventBlockId.timezone)["value"] event_description = parsed_form_data.get(AddTimelineEventBlockId.description) participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email ) event_timezone = event_timezone_selection if event_timezone_selection == "profile": participant_profile = get_user_profile_by_email(slack_client, user_email) if participant_profile.get("tz"): event_timezone = participant_profile.get("tz") event_dt = datetime.fromisoformat(f"{event_date}T{event_hour}:{event_minute}") event_dt_utc = pytz.timezone(event_timezone).localize(event_dt).astimezone(pytz.utc) event_service.log( db_session=db_session, source="Slack Plugin - Conversation Management", started_at=event_dt_utc, description=f'"{event_description}," said {participant.individual.name}', incident_id=incident_id, individual_id=participant.individual.id, ) send_ephemeral_message( client=slack_client, conversation_id=channel_id, user_id=user_id, text="Event sucessfully added to timeline.", )
def add_timeline_event_from_submitted_form(action: dict, db_session=None): """Adds event to incident timeline based on submitted form data.""" user_email = action["user"]["email"] submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) event_date = parsed_form_data.get(AddTimelineEventBlockFields.date) event_hour = parsed_form_data.get( AddTimelineEventBlockFields.hour)["value"] event_minute = parsed_form_data.get( AddTimelineEventBlockFields.minute)["value"] event_timezone_selection = parsed_form_data.get( AddTimelineEventBlockFields.timezone)["value"] event_description = parsed_form_data.get( AddTimelineEventBlockFields.description) incident_id = action["view"]["private_metadata"]["incident_id"] participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) event_timezone = event_timezone_selection if event_timezone_selection == "profile": participant_profile = get_user_profile_by_email( slack_client, user_email) if participant_profile.get("tz"): event_timezone = participant_profile.get("tz") event_dt = datetime.fromisoformat( f"{event_date}T{event_hour}:{event_minute}") event_dt_utc = pytz.timezone(event_timezone).localize(event_dt).astimezone( pytz.utc) event_service.log( db_session=db_session, source="Slack Plugin - Conversation Management", started_at=event_dt_utc, description= f'"{event_description}," said {participant.individual.name}', incident_id=incident_id, individual_id=participant.individual.id, )
def rating_feedback_from_submitted_form( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None, slack_client=None, ): """Adds rating and feeback to incident based on submitted form data.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) feedback = parsed_form_data.get(RatingFeedbackBlockId.feedback) rating = parsed_form_data.get(RatingFeedbackBlockId.rating)["value"] anonymous = parsed_form_data.get(RatingFeedbackBlockId.anonymous)["value"] feedback_in = FeedbackCreate(rating=rating, feedback=feedback, project=incident.project) feedback = feedback_service.create(db_session=db_session, feedback_in=feedback_in) incident.feedback.append(feedback) if anonymous == "": participant.feedback.append(feedback) db_session.add(participant) db_session.add(incident) db_session.commit() send_message( client=slack_client, conversation_id=user_id, text="Thank you for your feedback!", )
def member_joined_channel( user_email: str, incident_id: int, event: EventEnvelope, db_session=None, ): """Handles the member_joined_channel slack event.""" participant = incident_flows.incident_add_or_reactivate_participant_flow( user_email=user_email, incident_id=incident_id, db_session=db_session) # update participant metadata inviter_email = get_user_email(client=slack_client, user_id=event.event.inviter) added_by_participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=inviter_email) participant.added_by = added_by_participant participant.added_reason = event.event.text db_session.commit()
def rating_feedback_from_submitted_form( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, config: SlackConversationConfiguration = None, db_session=None, slack_client=None, ): """Adds rating and feeback to incident based on submitted form data.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email ) submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) feedback = parsed_form_data.get(RatingFeedbackBlockId.feedback) rating = parsed_form_data.get(RatingFeedbackBlockId.rating, {}).get("value") feedback_in = FeedbackCreate( rating=rating, feedback=feedback, project=incident.project, incident=incident ) feedback = feedback_service.create(db_session=db_session, feedback_in=feedback_in) incident.feedback.append(feedback) # we only really care if this exists, if it doesn't then flag is false if not parsed_form_data.get(RatingFeedbackBlockId.anonymous): participant.feedback.append(feedback) db_session.add(participant) db_session.add(incident) db_session.commit() send_message( client=slack_client, conversation_id=user_id, text="Thank you for your feedback!", )
async def check_command_restrictions(command: str, user_id: str, incident_id: int, db_session: Session) -> bool: """Checks the current user's role to determine what commands they are allowed to run.""" # some commands are sensitive and we only let non-participants execute them command_permissons = { SLACK_COMMAND_UPDATE_INCIDENT_SLUG: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], SLACK_COMMAND_ASSIGN_ROLE_SLUG: [ ParticipantRoleType.incident_commander, ParticipantRoleType.reporter, ParticipantRoleType.liaison, ParticipantRoleType.scribe, ], SLACK_COMMAND_REPORT_EXECUTIVE_SLUG: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], SLACK_COMMAND_REPORT_TACTICAL_SLUG: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], } # no permissions have been defined if command not in command_permissons.keys(): return True slack_async_client = dispatch_slack_service.create_slack_client( run_async=True) user_email = await dispatch_slack_service.get_user_email_async( slack_async_client, user_id) participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) # if any required role is active, allow command for current_role in participant.current_roles: for allowed_role in command_permissons[command]: if current_role.role == allowed_role: return True
def after_hours(user_email: str, incident_id: int, event: dict = None, db_session=None): """Notifies the user that this incident is current in after hours mode.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) # get their timezone from slack commander_info = dispatch_slack_service.get_user_info_by_email( slack_client, email=incident.commander.email) commander_tz = commander_info["tz"] if not is_business_hours(commander_tz): # send ephermal message blocks = [{ "type": "section", "text": { "type": "mrkdwn", "text": ((f"Responses may be delayed. The current incident priority is *{incident.incident_priority.name}*" f" and your message was sent outside of the Incident Commander's working hours (Weekdays, 9am-5pm, {commander_tz} timezone)." )), }, }] participant = participant_service.get_by_incident_id_and_email( incident_id=incident_id, email=user_email) if not participant.after_hours_notification: user_id = dispatch_slack_service.resolve_user( slack_client, user_email)["id"] dispatch_slack_service.send_ephemeral_message( slack_client, incident.conversation.channel_id, user_id, "", blocks=blocks) participant.after_hours_notification = True db_session.add(participant) db_session.commit()
def check_command_restrictions( config: SlackConfiguration, command: str, user_email: str, incident_id: int, db_session: Session ) -> bool: """Checks the current user's role to determine what commands they are allowed to run.""" # some commands are sensitive and we only let non-participants execute them command_permissons = { config.slack_command_update_incident: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], config.slack_command_assign_role: [ ParticipantRoleType.incident_commander, ParticipantRoleType.reporter, ParticipantRoleType.liaison, ParticipantRoleType.scribe, ], config.slack_command_report_executive: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], config.slack_command_report_tactical: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], } # no permissions have been defined if command not in command_permissons.keys(): return True participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email ) # if any required role is active, allow command for active_role in participant.active_roles: for allowed_role in command_permissons[command]: if active_role.role == allowed_role: return True
def increment_activity( config: SlackConversationConfiguration, user_id: str, user_email: str, channel_id: str, incident_id: int, event: EventEnvelope = None, db_session=None, slack_client=None, ): # increment activity for user participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) # member join also creates a message but they aren't yet a participant if participant: if participant.activity: participant.activity += 1 else: participant.activity = 1 db_session.commit()
def save_status_report( user_email: str, conditions: str, actions: str, needs: str, incident_id: int, db_session: SessionLocal, ): """Saves a new status report.""" # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) # we create a new status report status_report = create( db_session=db_session, conditions=conditions, actions=actions, needs=needs ) # we load the participant participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email ) # we save the status report participant.status_reports.append(status_report) incident.status_reports.append(status_report) db_session.add(participant) db_session.add(incident) db_session.commit() event_service.log( db_session=db_session, source="Incident Participant", description=f"{participant.individual.name} created a new status report", details={"conditions": conditions, "actions": actions, "needs": needs}, incident_id=incident_id, individual_id=participant.individual.id, )
def create_executive_report( user_email: str, incident_id: int, executive_report_in: ExecutiveReportCreate, organization_slug: str = None, db_session=None, ): """Creates an executive report.""" current_date = date.today().strftime("%B %d, %Y") current_status = executive_report_in.current_status overview = executive_report_in.overview next_steps = executive_report_in.next_steps # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) if not incident.incident_type.executive_template_document: raise ValidationError( [ ErrorWrapper( InvalidConfigurationError( msg="No executive report template defined."), loc="executive_template_document", ) ], model=ExecutiveReportCreate, ) # we fetch all previous executive reports executive_reports = get_all_by_incident_id_and_type( db_session=db_session, incident_id=incident_id, report_type=ReportTypes.executive_report) previous_executive_reports = [] for executive_report in executive_reports: previous_executive_reports.append( f"{executive_report.document.name} - {executive_report.document.weblink}\n" ) # we create a new executive report details = { "current_status": current_status, "overview": overview, "next_steps": next_steps } executive_report_in = ReportCreate( details=details, type=ReportTypes.executive_report, ) executive_report = create(db_session=db_session, report_in=executive_report_in) # we load the participant participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) # we save the executive report participant.reports.append(executive_report) incident.reports.append(executive_report) db_session.add(participant) db_session.add(incident) db_session.commit() event_service.log( db_session=db_session, source="Incident Participant", description= f"{participant.individual.name} created a new executive report", details={ "current_status": current_status, "overview": overview, "next_steps": next_steps }, incident_id=incident_id, individual_id=participant.individual.id, ) # we create a new document for the executive report storage_plugin = plugin_service.get_active_instance( db_session=db_session, project_id=incident.project.id, plugin_type="storage") executive_report_document_name = f"{incident.name} - Executive Report - {current_date}" executive_report_document = storage_plugin.instance.copy_file( folder_id=incident.storage.resource_id, file_id=incident.incident_type.executive_template_document.resource_id, name=executive_report_document_name, ) executive_report_document.update({ "name": executive_report_document_name, "resource_type": DocumentResourceTypes.executive, }) storage_plugin.instance.move_file( new_folder_id=incident.storage.resource_id, file_id=executive_report_document["id"]) event_service.log( db_session=db_session, source=storage_plugin.plugin.title, description="Executive report document added to storage", incident_id=incident.id, ) document_in = DocumentCreate( name=executive_report_document["name"], resource_id=executive_report_document["id"], resource_type=executive_report_document["resource_type"], project=incident.project, weblink=executive_report_document["weblink"], ) executive_report.document = document_service.create( db_session=db_session, document_in=document_in) incident.documents.append(executive_report.document) db_session.add(executive_report) db_session.add(incident) db_session.commit() event_service.log( db_session=db_session, source="Dispatch Core App", description="Executive report document added to incident", incident_id=incident.id, ) # we update the incident update document document_plugin = plugin_service.get_active_instance( db_session=db_session, project_id=incident.project.id, plugin_type="document") document_plugin.instance.update( executive_report_document["id"], name=incident.name, title=incident.title, current_date=current_date, current_status=current_status, overview=overview, next_steps=next_steps, previous_reports="\n".join(previous_executive_reports), commander_fullname=incident.commander.individual.name, commander_team=incident.commander.team, commander_weblink=incident.commander.individual.weblink, ) # we send the executive report to the notifications group send_executive_report_to_notifications_group(incident.id, executive_report, db_session) return executive_report
def monitor_link( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, config: SlackConversationConfiguration = None, db_session=None, slack_client=None, ): """Starts monitoring a link.""" button = MonitorButton.parse_raw(action["actions"][0]["value"]) incident = incident_service.get(db_session=db_session, incident_id=incident_id) plugin_instance = plugin_service.get_instance( db_session=db_session, plugin_instance_id=button.plugin_instance_id) creator_email = get_user_email(slack_client, action["user"]["id"]) creator = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident.id, email=creator_email) if button.action_type == "monitor": status = plugin_instance.instance.get_match_status( weblink=button.weblink) monitor_in = MonitorCreate( incident=incident, enabled=True, status=status, plugin_instance=plugin_instance, creator=creator, weblink=button.weblink, ) message_template = INCIDENT_MONITOR_CREATED_NOTIFICATION elif button.action_type == "ignore": monitor_in = MonitorCreate( incident=incident, enabled=False, plugin_instance=plugin_instance, creator=creator, weblink=button.weblink, ) message_template = INCIDENT_MONITOR_IGNORE_NOTIFICATION else: raise DispatchException( f"Unknown monitor action type. Type: {button.action_type}") monitor_service.create_or_update(db_session=db_session, monitor_in=monitor_in) notification_text = "Incident Notification" notification_type = "incident-notification" plugin = plugin_service.get_active_instance(db_session=db_session, plugin_type="conversation", project_id=incident.project.id) plugin.instance.send_ephemeral( channel_id, user_id, notification_text, message_template, notification_type, weblink=button.weblink, )
def send_incident_participant_announcement_message( participant_email: str, incident_id: int, db_session=SessionLocal ): """Announces a participant in the conversation.""" notification_text = "New Incident Participant" notification_type = MessageType.incident_notification notification_template = [] # we load the incident instance incident = incident_service.get(db_session=db_session, incident_id=incident_id) participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=participant_email ) contact_plugin = plugins.get(INCIDENT_PLUGIN_CONTACT_SLUG) participant_info = contact_plugin.get(participant_email) participant_name = participant_info["fullname"] participant_team = participant_info["team"] participant_department = participant_info["department"] participant_location = participant_info["location"] participant_weblink = participant_info["weblink"] convo_plugin = plugins.get(INCIDENT_PLUGIN_CONVERSATION_SLUG) participant_avatar_url = convo_plugin.get_participant_avatar_url(participant_email) participant_active_roles = participant_role_service.get_all_active_roles( db_session=db_session, participant_id=participant.id ) participant_roles = [] for role in participant_active_roles: participant_roles.append(role.role) blocks = [ {"type": "section", "text": {"type": "mrkdwn", "text": f"*{notification_text}*"}}, { "type": "section", "text": { "type": "mrkdwn", "text": ( f"*Name:* <{participant_weblink}|{participant_name}>\n" f"*Team*: {participant_team}, {participant_department}\n" f"*Location*: {participant_location}\n" f"*Incident Role(s)*: {(', ').join(participant_roles)}\n" ), }, "accessory": { "type": "image", "image_url": participant_avatar_url, "alt_text": participant_name, }, }, ] convo_plugin.send( incident.conversation.channel_id, notification_text, notification_template, notification_type, blocks=blocks, ) log.debug("Incident participant announcement message sent.")
def assign_role_flow(db_session: SessionLocal, incident: Incident, assignee_contact_info: dict, assignee_role: str): """Attempts to assign a role to a participant. Returns: str: - "role_assigned", if role assigned. - "role_not_assigned", if not role assigned. - "assignee_has_role", if assignee already has the role. """ # we get the participant that holds the role assigned to the assignee participant_with_assignee_role = participant_service.get_by_incident_id_and_role( db_session=db_session, incident_id=incident.id, role=assignee_role) # we get the participant 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 participant_with_assignee_role is assignee_participant: return "assignee_has_role" if participant_with_assignee_role: # we make them renounce to the role renounce_role( db_session=db_session, participant_id=participant_with_assignee_role.id, role_type=assignee_role, ) # we create a new role for the participant participant_role = create(db_session=db_session) # we assign the new role to the participant participant_with_assignee_role.participant_role.append( participant_role) # we commit the changes to the database db_session.add(participant_with_assignee_role) db_session.commit() log.debug( f"We made {participant_with_assignee_role.individual.name} renounce to the {assignee_role} role." ) if assignee_participant: # we make the assignee renounce to their current role assignee_participant_active_roles = get_active_roles( db_session=db_session, participant_id=assignee_participant.id) for active_role in assignee_participant_active_roles: if active_role.role != ParticipantRoleType.reporter: renounce_role( db_session=db_session, participant_id=assignee_participant.id, role_type=active_role.role, ) # we create a new role for the assignee assignee_participant_role = create(db_session=db_session, role=assignee_role) # we assign the new role to the assignee assignee_participant.participant_role.append(assignee_participant_role) # we commit the changes to the database db_session.add(assignee_participant) db_session.commit() log.debug( f"We assigned the {assignee_role} role to {assignee_contact_info['fullname']}." ) return "role_assigned" log.debug( f"We were not able to assign the {assignee_role} role to {assignee_contact_info['fullname']}." ) return "role_not_assigned"
def incident_assign_role_flow(assigner_email: str, incident_id: int, action: dict, db_session=None): """Runs the incident participant role assignment flow.""" assignee_user_id = action["submission"]["participant"] assignee_role = action["submission"]["role"] # we resolve the assignee's email address convo_plugin = plugins.get(INCIDENT_PLUGIN_CONVERSATION_SLUG) assignee_email = convo_plugin.get_participant_email(assignee_user_id) # 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 with the given role 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(db_session, incident, assignee_contact_info, assignee_role) if result == "assignee_has_role": # 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": # 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, )
def assign_role_flow(incident: "Incident", assignee_email: str, assignee_role: str, db_session: SessionLocal): """Attempts to assign a role to a participant. Returns: str: - "role_assigned", if role assigned. - "role_not_assigned", if not role assigned. - "assignee_has_role", if assignee already has the role. """ # we get the participant that holds the role assigned to the assignee participant_with_assignee_role = participant_service.get_by_incident_id_and_role( db_session=db_session, incident_id=incident.id, role=assignee_role) # we get the participant for the assignee assignee_participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident.id, email=assignee_email) if participant_with_assignee_role is assignee_participant: return "assignee_has_role" if participant_with_assignee_role: # we make the participant renounce to the role that has been given to the assignee participant_active_roles = get_all_active_roles( db_session=db_session, participant_id=participant_with_assignee_role.id) for participant_active_role in participant_active_roles: if participant_active_role.role == assignee_role: renounce_role(db_session=db_session, participant_role=participant_active_role) break # we check if the participant has other active roles participant_active_roles = get_all_active_roles( db_session=db_session, participant_id=participant_with_assignee_role.id) if participant_active_roles.count() == 0: # we give the participant a new participant role add_role( db_session=db_session, participant_id=participant_with_assignee_role.id, participant_role=ParticipantRoleType.participant, ) log.debug( f"We made {participant_with_assignee_role.individual.name} renounce to their {assignee_role} role." ) if assignee_participant: # we make the assignee renounce to the participant role, if they have it participant_active_roles = get_all_active_roles( db_session=db_session, participant_id=assignee_participant.id) for participant_active_role in participant_active_roles: if participant_active_role.role == ParticipantRoleType.participant: renounce_role(db_session=db_session, participant_role=participant_active_role) break # we give the assignee the new role add_role( db_session=db_session, participant_id=assignee_participant.id, participant_role=assignee_role, ) # we update the commander, reporter, scribe, or liaison foreign key if assignee_role == ParticipantRoleType.incident_commander: incident.commander_id = assignee_participant.id elif assignee_role == ParticipantRoleType.reporter: incident.reporter_id = assignee_participant.id elif assignee_role == ParticipantRoleType.scribe: incident.scribe_id = assignee_participant.id elif assignee_role == ParticipantRoleType.liaison: incident.liaison_id = assignee_participant.id # we add and commit the changes db_session.add(incident) db_session.commit() event_service.log( db_session=db_session, source="Dispatch Core App", description= f"{assignee_participant.individual.name} has been assigned the role of {assignee_role}", incident_id=incident.id, ) return "role_assigned" log.debug( f"We were not able to assign the {assignee_role} role to {assignee_email}." ) return "role_not_assigned"
def send_incident_participant_announcement_message(participant_email: str, incident: Incident, db_session: SessionLocal): """Announces a participant in the conversation.""" convo_plugin = plugin_service.get_active_instance( db_session=db_session, project_id=incident.project.id, plugin_type="conversation") if not convo_plugin: log.warning( "Incident participant announcement message not sent. No conversation plugin enabled." ) return notification_text = "New Incident Participant" notification_type = MessageType.incident_notification notification_template = [] participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident.id, email=participant_email) participant_info = {} contact_plugin = plugin_service.get_active_instance( db_session=db_session, project_id=incident.project.id, plugin_type="contact") if contact_plugin: participant_info = contact_plugin.instance.get(participant_email, db_session=db_session) participant_name = participant_info.get("fullname", "Unknown") participant_team = participant_info.get("team", "Unknown") participant_department = participant_info.get("department", "Unknown") participant_location = participant_info.get("location", "Unknown") participant_weblink = participant_info.get("weblink", DISPATCH_UI_URL) participant_active_roles = participant_role_service.get_all_active_roles( db_session=db_session, participant_id=participant.id) participant_roles = [] for role in participant_active_roles: participant_roles.append(role.role) participant_avatar_url = convo_plugin.instance.get_participant_avatar_url( participant_email) # TODO these shouldn't be raw blocks (kglisson) blocks = [ { "type": "section", "text": { "type": "mrkdwn", "text": f"*{notification_text}*" } }, { "type": "section", "text": { "type": "mrkdwn", "text": (f"*Name:* <{participant_weblink}|{participant_name}>\n" f"*Team*: {participant_team}, {participant_department}\n" f"*Location*: {participant_location}\n" f"*Incident Role(s)*: {(', ').join(participant_roles)}\n"), }, "accessory": { "type": "image", "image_url": participant_avatar_url, "alt_text": participant_name, }, }, ] convo_plugin.instance.send( incident.conversation.channel_id, notification_text, notification_template, notification_type, blocks=blocks, ) log.debug("Incident participant announcement message sent.")
def assign_role_flow(incident_id: int, assignee_contact_info: dict, assignee_role: str, db_session: SessionLocal): """Attempts to assign a role to a participant. Returns: str: - "role_assigned", if role assigned. - "role_not_assigned", if not role assigned. - "assignee_has_role", if assignee already has the role. """ # we get the participant that holds the role assigned to the assignee participant_with_assignee_role = participant_service.get_by_incident_id_and_role( db_session=db_session, incident_id=incident_id, role=assignee_role) # we get the participant 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 participant_with_assignee_role is assignee_participant: return "assignee_has_role" if participant_with_assignee_role: # we make the participant renounce to the role that has been given to the assignee participant_active_roles = get_all_active_roles( db_session=db_session, participant_id=participant_with_assignee_role.id) for participant_active_role in participant_active_roles: if participant_active_role.role == assignee_role: renounce_role(db_session=db_session, participant_role=participant_active_role) break # we check if the participant has other active roles participant_active_roles = get_all_active_roles( db_session=db_session, participant_id=participant_with_assignee_role.id) if participant_active_roles.count() == 0: # we give the participant a new participant role add_role( db_session=db_session, participant_id=participant_with_assignee_role.id, participant_role=ParticipantRoleType.participant, ) log.debug( f"We made {participant_with_assignee_role.individual.name} renounce to their {assignee_role} role." ) if assignee_participant: # we make the assignee renounce to the participant role, if they have it participant_active_roles = get_all_active_roles( db_session=db_session, participant_id=assignee_participant.id) for participant_active_role in participant_active_roles: if participant_active_role.role == ParticipantRoleType.participant: renounce_role(db_session=db_session, participant_role=participant_active_role) break # we give the assignee the new role add_role( db_session=db_session, participant_id=assignee_participant.id, participant_role=assignee_role, ) event_service.log( db_session=db_session, source="Dispatch Core App", description= f"{assignee_contact_info['fullname']} has been assigned the role of {assignee_role}", incident_id=incident_id, ) return "role_assigned" log.debug( f"We were not able to assign the {assignee_role} role to {assignee_contact_info['fullname']}." ) return "role_not_assigned"