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