Beispiel #1
0
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.",
    )
Beispiel #2
0
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
        )
Beispiel #3
0
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)
Beispiel #4
0
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."
    )
Beispiel #5
0
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)
Beispiel #6
0
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,
        )
Beispiel #7
0
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,
    )
Beispiel #8
0
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,
        )
Beispiel #9
0
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,
    )
Beispiel #10
0
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,
    )
Beispiel #11
0
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)
Beispiel #12
0
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,
    )
Beispiel #13
0
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."
        )
Beispiel #14
0
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
Beispiel #15
0
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)
Beispiel #16
0
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()
Beispiel #17
0
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]
        )
Beispiel #18
0
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,
    )
Beispiel #19
0
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.",
    )
Beispiel #20
0
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,
    )
Beispiel #21
0
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)
Beispiel #22
0
    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()
Beispiel #23
0
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)
Beispiel #24
0
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."
        )
Beispiel #25
0
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,
        )
Beispiel #26
0
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()
Beispiel #27
0
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.",
    )
Beispiel #28
0
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)
Beispiel #29
0
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)
Beispiel #30
0
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)