def update_participant_from_submitted_form( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None, slack_client=None, ): """Saves form data.""" submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) added_reason = parsed_form_data.get(UpdateParticipantBlockFields.reason_added) participant_id = int(parsed_form_data.get(UpdateParticipantBlockFields.participant)["value"]) selected_participant = participant_service.get( db_session=db_session, participant_id=participant_id ) participant_service.update( db_session=db_session, participant=selected_participant, participant_in=ParticipantUpdate(added_reason=added_reason), ) dispatch_slack_service.send_ephemeral_message( client=slack_client, conversation_id=channel_id, user_id=user_id, text="You have successfully updated the participant.", )
def create_rating_feedback_modal( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None, slack_client=None, ): """Creates a modal for rating and providing feedback about an incident.""" trigger_id = action["trigger_id"] incident = incident_service.get(db_session=db_session, incident_id=incident_id) if not incident: message = ( "Sorry, you cannot submit feedback about this incident. The incident does not exist." ) send_ephemeral_message(slack_client, channel_id, user_id, message) else: modal_create_template = rating_feedback_view(incident=incident) open_modal_with_user( client=slack_client, trigger_id=trigger_id, modal=modal_create_template )
def handle_engage_oncall_action( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, config: SlackConversationConfiguration = None, db_session=None, slack_client=None, ): """Adds and pages based on the oncall modal.""" oncall_service_external_id = action["submission"][ "oncall_service_external_id"] page = action["submission"]["page"] oncall_individual, oncall_service = incident_flows.incident_engage_oncall_flow( user_email, incident_id, oncall_service_external_id, page=page, db_session=db_session) if not oncall_individual and not oncall_service: message = "Could not engage oncall. Oncall service plugin not enabled." if not oncall_individual and oncall_service: message = f"A member of {oncall_service.name} is already in the conversation." if oncall_individual and oncall_service: message = f"You have successfully engaged {oncall_individual.name} from the {oncall_service.name} oncall rotation." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message)
def handle_update_incident_action( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None, slack_client=None, ): """Massages slack dialog data into something that Dispatch can use.""" submission = action["submission"] notify = True if submission["notify"] == "Yes" else False incident_in = IncidentUpdate( title=submission["title"], description=submission["description"], incident_type={"name": submission["type"]}, incident_priority={"name": submission["priority"]}, status=submission["status"], visibility=submission["visibility"], ) incident = incident_service.get(db_session=db_session, incident_id=incident_id) existing_incident = IncidentRead.from_orm(incident) incident_service.update(db_session=db_session, incident=incident, incident_in=incident_in) incident_flows.incident_update_flow(user_email, incident_id, existing_incident, notify) dispatch_slack_service.send_ephemeral_message( slack_client, channel_id, user_id, "You have sucessfully updated the incident." )
def handle_engage_oncall_action( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None, slack_client=None, ): """Adds and pages based on the oncall modal.""" oncall_service_id = action["submission"]["oncall_service_id"] page = action["submission"]["page"] oncall_individual, oncall_service = incident_flows.incident_engage_oncall_flow( user_email, incident_id, oncall_service_id, page=page, db_session=db_session) if not oncall_service or not oncall_individual: message = "Could not engage oncall. Oncall service plugin not enabled." else: message = f"You have successfully engaged {oncall_individual.name} from oncall rotation {oncall_service.name}." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message)
def ban_threads_warning( user_id: str, user_email: str, channel_id: str, incident_id: int, event: EventEnvelope = None, db_session=None, slack_client=None, ): """Sends the user an ephemeral message if they use threads.""" if not SLACK_BAN_THREADS: return if event.event.thread_ts: # we should be able to look for `subtype == message_replied` once this bug is fixed # https://api.slack.com/events/message/message_replied # From Slack: Bug alert! This event is missing the subtype field when dispatched # over the Events API. Until it is fixed, examine message events' thread_ts value. # When present, it's a reply. To be doubly sure, compare a thread_ts to the top-level ts # value, when they differ the latter is a reply to the former. message = "Please refrain from using threads in incident related channels. Threads make it harder for incident participants to maintain context." dispatch_slack_service.send_ephemeral_message( slack_client, channel_id, user_id, message, thread_ts=event.event.thread_ts, )
def list_participants(incident_id: int, command: dict = None, db_session=None): """Returns the list of incident participants to the user as an ephemeral message.""" blocks = [] blocks.append( {"type": "section", "text": {"type": "mrkdwn", "text": "*Incident Participants*"}} ) participants = participant_service.get_all_by_incident_id( db_session=db_session, incident_id=incident_id ) contact_plugin = plugins.get(INCIDENT_PLUGIN_CONTACT_SLUG) for participant in participants: if participant.is_active: participant_email = participant.individual.email 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"] participant_avatar_url = dispatch_slack_service.get_user_avatar_url( slack_client, 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.append( { "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, }, } ) blocks.append({"type": "divider"}) dispatch_slack_service.send_ephemeral_message( slack_client, command["channel_id"], command["user_id"], "Incident List Participants", blocks=blocks, )
def create_run_workflow_modal(incident_id: int, command: dict = None, db_session=None): """Creates a modal for running a workflow.""" trigger_id = command.get("trigger_id") incident = incident_service.get(db_session=db_session, incident_id=incident_id) workflows = workflow_service.get_enabled(db_session=db_session) if workflows: modal_create_template = build_workflow_blocks(incident=incident, workflows=workflows) dispatch_slack_service.open_modal_with_user( client=slack_client, trigger_id=trigger_id, modal=modal_create_template) else: blocks = [{ "type": "section", "text": { "type": "mrkdwn", "text": "No workflows are enabled. You can enable one in the Dispatch UI at /workflows.", }, }] dispatch_slack_service.send_ephemeral_message( slack_client, command["channel_id"], command["user_id"], "No workflows enabled.", blocks=blocks, )
def list_tasks( incident_id: int, command: dict = None, db_session=None, by_creator: str = None, by_assignee: str = None, ): """Returns the list of incident tasks to the user as an ephemeral message.""" blocks = [] for status in TaskStatus: blocks.append( { "type": "section", "text": {"type": "mrkdwn", "text": f"*{status.value} Incident Tasks*"}, } ) button_text = "Resolve" if status.value == TaskStatus.open else "Re-open" action_type = "resolve" if status.value == TaskStatus.open else "reopen" tasks = task_service.get_all_by_incident_id_and_status( db_session=db_session, incident_id=incident_id, status=status.value ) if by_creator or by_assignee: tasks = filter_tasks_by_assignee_and_creator(tasks, by_assignee, by_creator) for idx, task in enumerate(tasks): assignees = [f"<{a.individual.weblink}|{a.individual.name}>" for a in task.assignees] blocks.append( { "type": "section", "text": { "type": "mrkdwn", "text": ( f"*Description:* <{task.weblink}|{task.description}>\n" f"*Creator:* <{task.creator.individual.weblink}|{task.creator.individual.name}>\n" f"*Assignees:* {', '.join(assignees)}" ), }, "block_id": f"{ConversationButtonActions.update_task_status}-{task.status}-{idx}", "accessory": { "type": "button", "text": {"type": "plain_text", "text": button_text}, "value": f"{action_type}-{task.resource_id}", }, } ) blocks.append({"type": "divider"}) dispatch_slack_service.send_ephemeral_message( slack_client, command["channel_id"], command["user_id"], "Incident Task List", blocks=blocks, )
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 create_engage_oncall_dialog(incident_id: int, command: dict = None, db_session=None): """Creates a dialog to engage an oncall person.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) oncall_services = service_service.get_all_by_project_id_and_status( db_session=db_session, project_id=incident.project.id, is_active=True ) if not oncall_services.count(): blocks = [ { "type": "section", "text": { "type": "mrkdwn", "text": "No oncall services have been defined. You can define them in the Dispatch UI at /services", }, } ] dispatch_slack_service.send_ephemeral_message( slack_client, command["channel_id"], command["user_id"], "No oncall services defined", blocks=blocks, ) return oncall_service_options = [] for oncall_service in oncall_services: oncall_service_options.append( {"label": oncall_service.name, "value": oncall_service.external_id} ) page_options = [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}] dialog = { "callback_id": command["command"], "title": "Engage Oncall", "submit_label": "Engage", "elements": [ { "label": "Oncall Service", "type": "select", "name": "oncall_service_id", "options": oncall_service_options, }, { "label": "Page", "type": "select", "name": "page", "value": "No", "options": page_options, }, ], } dispatch_slack_service.open_dialog_with_user(slack_client, command["trigger_id"], dialog)
def report_incident_from_submitted_form( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, config: SlackConversationConfiguration = None, db_session: SessionLocal = None, slack_client=None, ): submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) # Send a confirmation to the user blocks = create_incident_reported_confirmation_message( title=parsed_form_data[IncidentBlockId.title], description=parsed_form_data[IncidentBlockId.description], incident_type=parsed_form_data[IncidentBlockId.type]["value"], incident_priority=parsed_form_data[IncidentBlockId.priority]["value"], ) send_ephemeral_message( client=slack_client, conversation_id=channel_id, user_id=user_id, text="", blocks=blocks, ) tags = [] for t in parsed_form_data.get(IncidentBlockId.tags, []): # we have to fetch as only the IDs are embedded in slack tag = tag_service.get(db_session=db_session, tag_id=int(t["value"])) tags.append(tag) project = {"name": parsed_form_data[IncidentBlockId.project]["value"]} incident_in = IncidentCreate( title=parsed_form_data[IncidentBlockId.title], description=parsed_form_data[IncidentBlockId.description], incident_type={"name": parsed_form_data[IncidentBlockId.type]["value"]}, incident_priority={"name": parsed_form_data[IncidentBlockId.priority]["value"]}, project=project, tags=tags, ) if not incident_in.reporter: incident_in.reporter = ParticipantUpdate(individual=IndividualContactRead(email=user_email)) # Create the incident incident = incident_service.create(db_session=db_session, incident_in=incident_in) incident_flows.incident_create_flow( incident_id=incident.id, db_session=db_session, organization_slug=incident.project.organization.slug, )
def update_incident_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, ): """Massages slack dialog data into something that Dispatch can use.""" submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) incident = incident_service.get(db_session=db_session, incident_id=incident_id) tags = [] for t in parsed_form_data.get(IncidentBlockId.tags, []): # we have to fetch as only the IDs are embedded in slack tag = tag_service.get(db_session=db_session, tag_id=int(t["value"])) tags.append(tag) incident_in = IncidentUpdate( title=parsed_form_data[IncidentBlockId.title], description=parsed_form_data[IncidentBlockId.description], resolution=parsed_form_data[IncidentBlockId.resolution], incident_type={"name": parsed_form_data[IncidentBlockId.type]["value"]}, incident_priority={"name": parsed_form_data[IncidentBlockId.priority]["value"]}, status=parsed_form_data[IncidentBlockId.status]["value"], tags=tags, ) previous_incident = IncidentRead.from_orm(incident) # we don't allow visibility to be set in slack so we copy it over incident_in.visibility = incident.visibility updated_incident = incident_service.update( db_session=db_session, incident=incident, incident_in=incident_in ) commander_email = updated_incident.commander.individual.email reporter_email = updated_incident.reporter.individual.email incident_flows.incident_update_flow( user_email, commander_email, reporter_email, incident_id, previous_incident, db_session=db_session, ) if updated_incident.status != IncidentStatus.closed: send_ephemeral_message( slack_client, channel_id, user_id, "You have sucessfully updated the incident." )
def after_hours(user_email: str, incident_id: int, 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) user_id = dispatch_slack_service.resolve_user(slack_client, user_email)["id"] # NOTE Limitations: Does not sync across instances. Does not survive webserver restart cache_key = create_cache_key(user_id, incident.conversation.channel_id) try: once_a_day_cache[cache_key] return except Exception: pass # we don't care if there is nothing here # bail early if we don't care for a given severity priority_types = [ IncidentPriorityType.info, IncidentPriorityType.low, IncidentPriorityType.medium, ] if incident.incident_priority.name.lower() not in priority_types: return # 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 severity is *{incident.incident_severity.name}*" f" and your message was sent outside of the incident commander's working hours (Weekdays, 9am-5pm, {commander_tz})." )), }, }] dispatch_slack_service.send_ephemeral_message( slack_client, incident.conversation.channel_id, user_id, "", blocks=blocks) once_a_day_cache[cache_key] = True
def report_incident_from_submitted_form( user_id: str, user_email: str, incident_id: int, action: dict, db_session: Session = Depends(get_db), ): submitted_form = action.get("view") # Fetch channel id from private metadata field channel_id = submitted_form.get("private_metadata") parsed_form_data = parse_submitted_form(submitted_form) requested_form_title = parsed_form_data.get(IncidentSlackViewBlockId.title) requested_form_description = parsed_form_data.get( IncidentSlackViewBlockId.description) requested_form_incident_type = parsed_form_data.get( IncidentSlackViewBlockId.type) requested_form_incident_priority = parsed_form_data.get( IncidentSlackViewBlockId.priority) # send a confirmation to the user msg_template = create_incident_reported_confirmation_msg( title=requested_form_title, incident_type=requested_form_incident_type.get("value"), incident_priority=requested_form_incident_priority.get("value"), ) dispatch_slack_service.send_ephemeral_message( client=slack_client, conversation_id=channel_id, user_id=user_id, text="", blocks=msg_template, ) # create the incident incident = incident_service.create( db_session=db_session, title=requested_form_title, status=IncidentStatus.active, description=requested_form_description, incident_type=requested_form_incident_type, incident_priority=requested_form_incident_priority, reporter_email=user_email, ) incident_flows.incident_create_flow(incident_id=incident.id)
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_user_to_conversation( user_id: str, user_email: str, incident_id: int, action: dict, db_session=None ): """Adds a user to a conversation.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) if incident.status == IncidentStatus.closed: message = f"Sorry, we cannot add you to a closed incident. Please reach out to the incident commander ({incident.commander.name}) for details." dispatch_slack_service.send_ephemeral_message( slack_client, action["container"]["channel_id"], user_id, message ) else: dispatch_slack_service.add_users_to_conversation( slack_client, incident.conversation.channel_id, [user_id] )
def list_workflows( user_id: str, user_email: str, channel_id: str, incident_id: int, command: dict = None, db_session=None, slack_client=None, ): """Returns the list of incident workflows to the user as an ephemeral message.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) blocks = [] blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": "*Incident Workflows*" } }) for w in incident.workflow_instances: artifact_links = "" for a in w.artifacts: artifact_links += f"- <{a.weblink}|{a.name}> \n" blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": (f"*Name:* <{w.weblink}|{w.workflow.name}>\n" f"*Workflow Description:* {w.workflow.description}\n" f"*Run Reason:* {w.run_reason}\n" f"*Creator:* {w.creator.individual.name}\n" f"*Status:* {w.status}\n" f"*Artifacts:* \n {artifact_links}"), }, }) blocks.append({"type": "divider"}) dispatch_slack_service.send_ephemeral_message( slack_client, channel_id, user_id, "Incident Workflow List", blocks=blocks, )
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 list_tasks( incident_id: int, command: dict = None, db_session=None, by_creator: str = None, by_assignee: str = None, ): """Returns the list of incident tasks to the user as an ephemeral message.""" blocks = [] for status in TaskStatus: blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": f"*{status.value} Incident Tasks*" }, }) tasks = task_service.get_all_by_incident_id_and_status( db_session=db_session, incident_id=incident_id, status=status.value) if by_creator or by_assignee: tasks = filter_tasks_by_assignee_and_creator( tasks, by_assignee, by_creator) for task in tasks: assignees = [a.individual.email for a in task.assignees] blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": (f"*Description:* <{task.weblink}|{task.description}>\n" f"*Assignees:* {', '.join(assignees)}"), }, }) blocks.append({"type": "divider"}) dispatch_slack_service.send_ephemeral_message( slack_client, command["channel_id"], command["user_id"], "Incident List Tasks", blocks=blocks, )
def report_incident_from_submitted_form( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session: SessionLocal = None, slack_client=None, ): submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) # Send a confirmation to the user blocks = create_incident_reported_confirmation_message( title=parsed_form_data[IncidentBlockId.title], description=parsed_form_data[IncidentBlockId.description], incident_type=parsed_form_data[IncidentBlockId.type]["value"], incident_priority=parsed_form_data[IncidentBlockId.priority]["value"], ) send_ephemeral_message( client=slack_client, conversation_id=channel_id, user_id=user_id, text="", blocks=blocks, ) tags = [] for t in parsed_form_data.get(IncidentBlockId.tags, []): tags.append({"id": t["value"]}) incident_in = IncidentCreate( title=parsed_form_data[IncidentBlockId.title], description=parsed_form_data[IncidentBlockId.description], incident_type={"name": parsed_form_data[IncidentBlockId.type]["value"]}, incident_priority={"name": parsed_form_data[IncidentBlockId.priority]["value"]}, project={"name": parsed_form_data[IncidentBlockId.project]["value"]}, reporter=IndividualContactCreate(email=user_email), tags=tags, ) # Create the incident incident = incident_service.create(db_session=db_session, incident_in=incident_in) incident_flows.incident_create_flow(incident_id=incident.id, db_session=db_session)
def wrapper(*args, **kwargs): background = False if not kwargs.get("db_session"): db_session = SessionLocal() background = True kwargs["db_session"] = db_session if not kwargs.get("slack_client"): slack_client = dispatch_slack_service.create_slack_client() kwargs["slack_client"] = slack_client try: metrics_provider.counter("function.call.counter", tags={ "function": fullname(func), "slack": True }) start = time.perf_counter() result = func(*args, **kwargs) elapsed_time = time.perf_counter() - start metrics_provider.timer( "function.elapsed.time", value=elapsed_time, tags={ "function": fullname(func), "slack": True }, ) return result except Exception as e: # we generate our own guid for now, maybe slack provides us something we can use? slack_interaction_guid = str(uuid.uuid4()) log.exception( e, extra=dict(slack_interaction_guid=slack_interaction_guid)) # notify the user the interaction failed user_id = args[0] channel_id = args[2] message = f"Sorry, we've run into an unexpected error. For help, please reach out to the incident commander and provide them with the following token: {slack_interaction_guid}." dispatch_slack_service.send_ephemeral_message( kwargs["slack_client"], channel_id, user_id, message) finally: if background: kwargs["db_session"].close()
def report_incident_from_submitted_form( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session: Session = None, slack_client=None, ): submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) requested_form_title = parsed_form_data.get(IncidentSlackViewBlockId.title) requested_form_description = parsed_form_data.get(IncidentSlackViewBlockId.description) requested_form_incident_type = parsed_form_data.get(IncidentSlackViewBlockId.type) requested_form_incident_priority = parsed_form_data.get(IncidentSlackViewBlockId.priority) # Send a confirmation to the user blocks = create_incident_reported_confirmation_message( title=requested_form_title, description=requested_form_description, incident_type=requested_form_incident_type.get("value"), incident_priority=requested_form_incident_priority.get("value"), ) dispatch_slack_service.send_ephemeral_message( client=slack_client, conversation_id=channel_id, user_id=user_id, text="", blocks=blocks, ) # Create the incident incident = incident_service.create( db_session=db_session, title=requested_form_title, status=IncidentStatus.active, description=requested_form_description, incident_type=requested_form_incident_type, incident_priority=requested_form_incident_priority, reporter_email=user_email, tags=[], # The modal does not currently support tags ) incident_flows.incident_create_flow(incident_id=incident.id)
def update_incident_from_submitted_form( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None, slack_client=None, ): """Massages slack dialog data into something that Dispatch can use.""" submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) tags = [] for t in parsed_form_data.get(IncidentBlockId.tags, []): tags.append({"id": t["value"]}) incident_in = IncidentUpdate( title=parsed_form_data[IncidentBlockId.title], description=parsed_form_data[IncidentBlockId.description], incident_type={"name": parsed_form_data[IncidentBlockId.type]["value"]}, incident_priority={"name": parsed_form_data[IncidentBlockId.priority]["value"]}, status=parsed_form_data[IncidentBlockId.status]["value"], tags=tags, ) incident = incident_service.get(db_session=db_session, incident_id=incident_id) existing_incident = IncidentRead.from_orm(incident) # we don't allow visibility to be set in slack so we copy it over incident_in.visibility = incident.visibility updated_incident = incident_service.update( db_session=db_session, incident=incident, incident_in=incident_in ) incident_flows.incident_update_flow( user_email, incident_id, existing_incident, db_session=db_session ) if updated_incident.status != IncidentStatus.closed: send_ephemeral_message( slack_client, channel_id, user_id, "You have sucessfully updated the incident." )
def create_run_workflow_modal( user_id: str, user_email: str, channel_id: str, incident_id: int, command: dict, config: SlackConversationConfiguration = None, db_session=None, slack_client=None, ): """Creates a modal for running a workflow.""" trigger_id = command.get("trigger_id") incident = incident_service.get(db_session=db_session, incident_id=incident_id) workflows = workflow_service.get_enabled(db_session=db_session) workflows = [x for x in workflows if x.plugin_instance.enabled] if workflows: modal_create_template = run_workflow_view(incident=incident, workflows=workflows) open_modal_with_user(client=slack_client, trigger_id=trigger_id, modal=modal_create_template) else: blocks = [{ "type": "section", "text": { "type": "mrkdwn", "text": "No workflows are enabled or workflows plugin is not enabled. You can enable one in the Dispatch UI at /workflows.", }, }] send_ephemeral_message( slack_client, command["channel_id"], command["user_id"], "No workflows enabled.", blocks=blocks, )
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 update_notifications_group_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, ): """Updates notifications group based on submitted form data.""" submitted_form = action.get("view") parsed_form_data = parse_submitted_form(submitted_form) current_members = ( submitted_form["blocks"][1]["element"]["initial_value"].replace(" ", "").split(",") ) updated_members = ( parsed_form_data.get(UpdateNotificationsGroupBlockId.update_members) .replace(" ", "") .split(",") ) members_added = list(set(updated_members) - set(current_members)) members_removed = list(set(current_members) - set(updated_members)) incident = incident_service.get(db_session=db_session, incident_id=incident_id) group_plugin = plugin_service.get_active_instance( db_session=db_session, project_id=incident.project.id, plugin_type="participant-group" ) group_plugin.instance.add(incident.notifications_group.email, members_added) group_plugin.instance.remove(incident.notifications_group.email, members_removed) send_ephemeral_message( client=slack_client, conversation_id=channel_id, user_id=user_id, text="You have successfully updated the notifications group.", )
def add_user_to_conversation( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None, slack_client=None, ): """Adds a user to a conversation.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) if incident.status == IncidentStatus.closed: message = f"Sorry, we cannot add you to a closed incident. Please reach out to the incident commander ({incident.commander.individual.name}) for details." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message) else: dispatch_slack_service.add_users_to_conversation( slack_client, incident.conversation.channel_id, [user_id] ) message = f"Success! We've added you to incident {incident.name}. Please check your side bar for the new channel." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message)
def update_task_status( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, db_session=None, slack_client=None, ): """Updates a task based on user input.""" action_type, external_task_id_b64 = action["actions"][0]["value"].split( "-") external_task_id = base64_decode(external_task_id_b64) resolve = True if action_type == "reopen": resolve = False # we only update the external task allowing syncing to care of propagation to dispatch task = task_service.get_by_resource_id(db_session=db_session, resource_id=external_task_id) # avoid external calls if we are already in the desired state if resolve and task.status == TaskStatus.resolved: message = "Task is already resolved." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message) return if not resolve and task.status == TaskStatus.open: message = "Task is already open." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message) return # we don't currently have a good way to get the correct file_id (we don't store a task <-> relationship) # lets try in both the incident doc and PIR doc drive_task_plugin = plugin_service.get_active( db_session=db_session, project_id=task.incident.project.id, plugin_type="task") try: file_id = task.incident.incident_document.resource_id drive_task_plugin.instance.update(file_id, external_task_id, resolved=resolve) except Exception: file_id = task.incident.incident_review_document.resource_id drive_task_plugin.instance.update(file_id, external_task_id, resolved=resolve) status = "resolved" if task.status == TaskStatus.open else "re-opened" message = f"Task successfully {status}." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message)
def add_user_to_tactical_group( user_id: str, user_email: str, channel_id: str, incident_id: int, action: dict, config: SlackConversationConfiguration = None, db_session=None, slack_client=None, ): """Adds a user to the incident tactical group.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) if not incident: message = "Sorry, we cannot add you to this incident. It does not exist." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message) else: incident_flows.add_participant_to_tactical_group(user_email=user_email, incident=incident, db_session=db_session) message = f"Success! We've subscribed you to incident {incident.name}. You will receive all tactical reports about this incident." dispatch_slack_service.send_ephemeral_message(slack_client, channel_id, user_id, message)