Skip to content

Commit

Permalink
Latest, adds slash commands, better escalate flow, bookmarks, and more
Browse files Browse the repository at this point in the history
  • Loading branch information
wssheldon committed Mar 29, 2024
1 parent d2c3e6d commit 71032b7
Show file tree
Hide file tree
Showing 19 changed files with 569 additions and 218 deletions.
291 changes: 154 additions & 137 deletions src/dispatch/case/flows.py

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/dispatch/case/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pydantic import validator
from sqlalchemy import (
Boolean,
Column,
DateTime,
ForeignKey,
Expand Down Expand Up @@ -88,6 +89,8 @@ class Case(Base, TimeStampMixin, ProjectMixin):
escalated_at = Column(DateTime)
closed_at = Column(DateTime)

dedicated_channel = Column(Boolean, default=False)

search_vector = Column(
TSVectorType(
"name", "title", "description", weights={"name": "A", "title": "B", "description": "C"}
Expand Down Expand Up @@ -169,6 +172,14 @@ def participant_observer(self, participants):
self.participants_team = Counter(p.team for p in participants).most_common(1)[0][0]
self.participants_location = Counter(p.location for p in participants).most_common(1)[0][0]

@property
def has_channel(self) -> bool:
return True if not self.conversation.thread_id else False

@property
def has_thread(self) -> bool:
return True if self.conversation.thread_id else False


class SignalRead(DispatchBase):
id: PrimaryKey
Expand Down Expand Up @@ -222,6 +233,7 @@ class CaseCreate(CaseBase):
case_priority: Optional[CasePriorityCreate]
case_severity: Optional[CaseSeverityCreate]
case_type: Optional[CaseTypeCreate]
dedicated_channel: Optional[bool]
project: Optional[ProjectRead]
reporter: Optional[ParticipantUpdate]
tags: Optional[List[TagRead]] = []
Expand Down
1 change: 1 addition & 0 deletions src/dispatch/case/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def create(*, db_session, case_in: CaseCreate, current_user: DispatchUser = None
description=case_in.description,
project=project,
status=case_in.status,
dedicated_channel=case_in.dedicated_channel,
tags=tag_objs,
)

Expand Down
64 changes: 46 additions & 18 deletions src/dispatch/conversation/flows.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging

from typing import TypeVar, List
from sqlalchemy.orm import Session

from dispatch.case.models import Case
from dispatch.conference.models import Conference
Expand All @@ -13,18 +13,21 @@
from dispatch.ticket.models import Ticket
from dispatch.utils import deslug_and_capitalize_resource_type
from dispatch.config import DISPATCH_UI_URL
from dispatch.types import Subject

from .models import Conversation, ConversationCreate
from .service import create

log = logging.getLogger(__name__)


Resource = TypeVar("Resource", Document, Conference, Storage, Ticket)
Resource = Document | Conference | Storage | Ticket


def create_case_conversation(
case: Case, conversation_target: str, db_session: SessionLocal, use_channel: bool = False
case: Case,
conversation_target: str,
db_session: Session,
):
"""Create external communication conversation."""

Expand All @@ -40,18 +43,26 @@ def create_case_conversation(

conversation = None

use_channel = True
# This case is a thread version, we send a new messaged (threaded) to the conversation target
# for the configured case type
if conversation_target:
try:
if not use_channel:
if not case.dedicated_channel:
conversation = plugin.instance.create_threaded(
case=case,
conversation_id=conversation_target,
db_session=db_session,
)
else:
conversation = plugin.instance.create(name=case.name)
except Exception as e:
# TODO: consistency across exceptions
log.exception(e)

# otherwise, it must be a channel based case.
if case.dedicated_channel:
try:
conversation = plugin.instance.create(
name=f"case-{case.name}",
)
except Exception as e:
# TODO: consistency across exceptions
log.exception(e)
Expand Down Expand Up @@ -219,16 +230,23 @@ def set_conversation_topic(incident: Incident, db_session: SessionLocal):
log.exception(e)


def add_conversation_bookmark(incident: Incident, resource: Resource, db_session: SessionLocal):
def add_conversation_bookmark(
db_session: Session,
subject: Subject,
resource: Resource,
title: str | None = None,
):
"""Adds a conversation bookmark."""
if not incident.conversation:
if not subject.conversation:
log.warning(
f"Conversation bookmark {resource.name.lower()} not added. No conversation available for this incident."
f"Conversation bookmark {resource.name.lower()} not added. No conversation available."
)
return

plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=incident.project.id, plugin_type="conversation"
db_session=db_session,
project_id=subject.project.id,
plugin_type="conversation",
)
if not plugin:
log.warning(
Expand All @@ -237,29 +255,33 @@ def add_conversation_bookmark(incident: Incident, resource: Resource, db_session
return

try:
title = deslug_and_capitalize_resource_type(resource.resource_type)
if not title:
title = deslug_and_capitalize_resource_type(resource.resource_type)
(
plugin.instance.add_bookmark(
incident.conversation.channel_id,
subject.conversation.channel_id,
resource.weblink,
title=title,
)
if resource
else log.warning(
f"{resource.name} bookmark not added. No {resource.name.lower()} available for this incident."
f"{resource.name} bookmark not added. No {resource.name.lower()} available for subject.."
)
)
except Exception as e:
event_service.log_incident_event(
db_session=db_session,
source="Dispatch Core App",
description=f"Adding the {resource.name.lower()} bookmark failed. Reason: {e}",
incident_id=incident.id,
incident_id=subject.id,
)
log.exception(e)


def add_conversation_bookmarks(incident: Incident, db_session: SessionLocal):
def add_conversation_bookmarks(
incident: Incident,
db_session: Session,
):
"""Adds the conversation bookmarks."""
if not incident.conversation:
log.warning(
Expand Down Expand Up @@ -339,7 +361,11 @@ def add_conversation_bookmarks(incident: Incident, db_session: SessionLocal):
log.exception(e)


def add_case_participants(case: Case, participant_emails: List[str], db_session: SessionLocal):
def add_case_participants(
case: Case,
participant_emails: list[str],
db_session: Session,
):
"""Adds one or more participants to the case conversation."""
if not case.conversation:
log.warning(
Expand Down Expand Up @@ -373,7 +399,9 @@ def add_case_participants(case: Case, participant_emails: List[str], db_session:


def add_incident_participants(
incident: Incident, participant_emails: List[str], db_session: SessionLocal
incident: Incident,
participant_emails: list[str],
db_session: Session,
):
"""Adds one or more participants to the incident conversation."""
if not incident.conversation:
Expand Down
12 changes: 4 additions & 8 deletions src/dispatch/conversation/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,20 @@ def get_by_channel_id_ignoring_channel_type(
Gets a conversation by its id ignoring the channel type, and updates the
channel id in the database if the channel type has changed.
"""
channel_id_without_type = channel_id[1:]

conversation = None

query = db_session.query(Conversation).filter(
Conversation.channel_id.contains(channel_id_without_type)
)
conversations = db_session.query(Conversation).filter(Conversation.channel_id == channel_id)

# The code below disambiguates between incident threads, case threads, and incident messages
if not thread_id:
# assume incident message
conversation = query.first()
conversation = conversations.first()

if not conversation:
conversation = query.filter(Conversation.thread_id == thread_id).one_or_none()
conversation = conversations.filter(Conversation.thread_id == thread_id).one_or_none()

if not conversation:
conversation = query.one_or_none()
conversation = conversations.one_or_none()

if conversation:
if channel_id[0] != conversation.channel_id[0]:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Creates a column on the Case
Revision ID: 3a33bc153e7e
Revises: 91bd05855ad1
Create Date: 2024-03-28 16:28:06.148971
"""

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "3a33bc153e7e"
down_revision = "91bd05855ad1"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("case", sa.Column("dedicated_channel", sa.Boolean(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("case", "dedicated_channel")
# ### end Alembic commands ###
6 changes: 6 additions & 0 deletions src/dispatch/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ class EventType(DispatchEnum):
participant_updated = "Participant updated" # for added/removed users and role changes
imported_message = "Imported message" # for stopwatch-reacted messages from Slack
custom_event = "Custom event" # for user-added events (new feature)


class SubjectNames(DispatchEnum):
CASE = "Case"
INCIDENT = "Incident"
SIGNAL = "Signal"
42 changes: 37 additions & 5 deletions src/dispatch/incident/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
send_incident_management_help_tips_message,
send_incident_new_role_assigned_notification,
send_incident_open_tasks_ephemeral_message,
send_incident_participant_announcement_message,
send_participant_announcement_message,
send_incident_rating_feedback_message,
send_incident_review_document_notification,
# send_incident_suggested_reading_messages,
Expand Down Expand Up @@ -229,7 +229,24 @@ def incident_create_resources(*, incident: Incident, db_session=None) -> Inciden
conversation_flows.set_conversation_topic(incident, db_session)

# we set the conversation bookmarks
conversation_flows.add_conversation_bookmarks(incident, db_session)
# conversation_flows.add_conversation_bookmarks(incident, db_session)
bookmarks = [
# resource, title
(incident.incident_document, None), # generated by resource name
(incident.ticket, "Incident Ticket"),
(incident.conference, "Incident Bridge"),
(incident.storage, "Incident Storage"),
]
for resource, title in bookmarks:
if not resource:
continue

conversation_flows.add_conversation_bookmark(
subject=incident,
resource=resource,
db_session=db_session,
title=title,
)

# we defer this setup for all resolved incident roles until after resources have been created
roles = ["reporter", "commander", "liaison", "scribe"]
Expand Down Expand Up @@ -261,11 +278,22 @@ def incident_create_resources(*, incident: Incident, db_session=None) -> Inciden

# we add the participant to the conversation
conversation_flows.add_incident_participants(
incident=incident, participant_emails=[user_email], db_session=db_session
incident=incident,
participant_emails=[user_email],
db_session=db_session,
)

# we announce the participant in the conversation
send_incident_participant_announcement_message(user_email, incident, db_session)
try:
send_participant_announcement_message(
participant_email=user_email,
subject=incident,
db_session=db_session,
)
except Exception as e:
log.warning(
f"Could not send participant announcement message to {user_email} in incident {incident.name}: {e}"
)

# we send the welcome messages to the participant
send_incident_welcome_participant_messages(user_email, incident, db_session)
Expand Down Expand Up @@ -952,7 +980,11 @@ def incident_add_or_reactivate_participant_flow(
)

# we announce the participant in the conversation
send_incident_participant_announcement_message(user_email, incident, db_session)
send_participant_announcement_message(
participant_email=user_email,
subject=incident,
db_session=db_session,
)

# we send the welcome messages to the participant
send_incident_welcome_participant_messages(user_email, incident, db_session)
Expand Down
Loading

0 comments on commit 71032b7

Please sign in to comment.