Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/dispatch/conversation/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,14 +296,14 @@ def get_topic_text(subject: Subject) -> str:
"""Returns the topic details based on subject"""
if isinstance(subject, Incident):
return (
f":helmet_with_white_cross: {subject.commander.individual.name}, {subject.commander.team} | "
f"⛑️ {subject.commander.individual.name}, {subject.commander.team} | "
f"Status: {subject.status} | "
f"Type: {subject.incident_type.name} | "
f"Severity: {subject.incident_severity.name} | "
f"Priority: {subject.incident_priority.name}"
)
return (
f":helmet_with_white_cross: {subject.assignee.individual.name}, {subject.assignee.team} | "
f"⛑️ {subject.assignee.individual.name}, {subject.assignee.team} | "
f"Status: {subject.status} | "
f"Type: {subject.case_type.name} | "
f"Severity: {subject.case_severity.name} | "
Expand Down
53 changes: 34 additions & 19 deletions src/dispatch/messaging/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ class MessageType(DispatchEnum):


INCIDENT_NAME_WITH_ENGAGEMENT = {
"title": ":rotating_light: {{name}} Incident Notification",
"title": "🚨 {{name}} Incident Notification",
"title_link": "{{ticket_weblink}}",
"text": NOTIFICATION_PURPOSES_FYI,
"buttons": [
Expand Down Expand Up @@ -469,7 +469,7 @@ class MessageType(DispatchEnum):
}

INCIDENT_NAME_WITH_ENGAGEMENT_NO_SELF_JOIN = {
"title": ":rotating_light: {{name}} Incident Notification",
"title": "🚨 {{name}} Incident Notification",
"title_link": "{{ticket_weblink}}",
"text": NOTIFICATION_PURPOSES_FYI,
"buttons": [
Expand All @@ -482,13 +482,13 @@ class MessageType(DispatchEnum):
}

CASE_NAME = {
"title": ":briefcase: {{name}} Case Notification",
"title": "💼 {{name}} Case Notification",
"title_link": "{{ticket_weblink}}",
"text": NOTIFICATION_PURPOSES_FYI,
}

CASE_NAME_WITH_ENGAGEMENT = {
"title": ":briefcase: {{name}} Case Notification",
"title": "💼 {{name}} Case Notification",
"title_link": "{{ticket_weblink}}",
"text": NOTIFICATION_PURPOSES_FYI,
"buttons": [
Expand All @@ -514,32 +514,39 @@ class MessageType(DispatchEnum):
}

CASE_NAME_WITH_ENGAGEMENT_NO_SELF_JOIN = {
"title": ":briefcase: {{name}} Case Notification",
"title": "💼 {{name}} Case Notification",
"title_link": "{{ticket_weblink}}",
"text": NOTIFICATION_PURPOSES_FYI,
}


CASE_STATUS_CHANGE = {
"title": "*{% set status_emojis = {'Closed': ':white_check_mark:', 'New': ':new:', 'Triage': ':mag:', 'Stable': ':shield:', 'Escalated': ':arrow_up:'} %}{{ status_emojis.get(case_status_new, ':arrows_counterclockwise:') }} Status Change:* {{ case_status_old }} → {{ case_status_new }}",
"title": "{% set status_emojis = {'Closed': '✅', 'New': '🆕', 'Triage': '🔍', 'Stable': '🛡️', 'Escalated': '⬆️'} %}{{ status_emojis.get(case_status_new, '🔄') }} Status Change",
"text": "{{ case_status_old }} → {{ case_status_new }}",
}

CASE_TYPE_CHANGE = {"title": "*:label: Case Type Change:* {{ case_type_old }} → {{ case_type_new }}"}
CASE_TYPE_CHANGE = {
"title": "🏷️ Case Type Change",
"text": "{{ case_type_old }} → {{ case_type_new }}",
}

CASE_SEVERITY_CHANGE = {
"title": "*{% if case_severity_old.view_order < case_severity_new.view_order %}:arrow_up:{% elif case_severity_old.view_order > case_severity_new.view_order %}:arrow_down:{% else %}:left_right_arrow:{% endif %} Severity Change:* {{ case_severity_old.name }} → {{ case_severity_new.name }}",
"title": "{% if case_severity_old.view_order < case_severity_new.view_order %}⬆️{% elif case_severity_old.view_order > case_severity_new.view_order %}⬇️{% else %}↔️{% endif %} Severity Change",
"text": "{{ case_severity_old.name }} → {{ case_severity_new.name }}",
}

CASE_PRIORITY_CHANGE = {
"title": "*{% if case_priority_old.view_order < case_priority_new.view_order %}:arrow_up:{% elif case_priority_old.view_order > case_priority_new.view_order %}:arrow_down:{% else %}:left_right_arrow:{% endif %} Priority Change:* {{ case_priority_old.name }} → {{ case_priority_new.name }}",
"title": "{% if case_priority_old.view_order < case_priority_new.view_order %}⬆️{% elif case_priority_old.view_order > case_priority_new.view_order %}⬇️{% else %}↔️{% endif %} Priority Change",
"text": "{{ case_priority_old.name }} → {{ case_priority_new.name }}",
}

CASE_VISIBILITY_CHANGE = {
"title": "*{% set visibility_emojis = {'Open': ':unlock:', 'Restricted': ':lock:'} %}{{ visibility_emojis.get(case_visibility_new, ':eye:') }} Visibility Change:* {{ case_visibility_old }} → {{ case_visibility_new }}",
"title": "{% set visibility_emojis = {'Open': '🔓', 'Restricted': '🔒'} %}{{ visibility_emojis.get(case_visibility_new, '👁️') }} Visibility Change",
"text": "{{ case_visibility_old }} → {{ case_visibility_new }}",
}

INCIDENT_NAME = {
"title": ":rotating_light: {{name}} Incident Notification",
"title": "🚨 {{name}} Incident Notification",
"title_link": "{{ticket_weblink}}",
"text": NOTIFICATION_PURPOSES_FYI,
}
Expand All @@ -552,9 +559,9 @@ class MessageType(DispatchEnum):

INCIDENT_SUMMARY = {"title": "Summary", "text": "{{summary}}"}

INCIDENT_TITLE = {"title": "*:memo: Title:* {{title}}"}
INCIDENT_TITLE = {"title": "📝 Title", "text": "{{title}}"}

CASE_TITLE = {"title": "*:memo: Title:* {{title}}"}
CASE_TITLE = {"title": "📝 Title", "text": "{{title}}"}

CASE_STATUS = {
"title": "Status - {{status}}",
Expand Down Expand Up @@ -615,7 +622,8 @@ class MessageType(DispatchEnum):
}

INCIDENT_COMMANDER = {
"title": ":firefighter: Commander: <{{commander_weblink}}|{{commander_fullname}}, {{commander_team}}>",
"title": "🧑‍🚒 Commander - {{commander_fullname}}, {{commander_team}}",
"title_link": "{{commander_weblink}}",
"text": INCIDENT_COMMANDER_DESCRIPTION,
}

Expand Down Expand Up @@ -662,17 +670,23 @@ class MessageType(DispatchEnum):
}

INCIDENT_STATUS_CHANGE = {
"title": "*{% set status_emojis = {'Closed': ':white_check_mark:', 'Stable': ':shield:', 'Active': ':fire:'} %}{{ status_emojis.get(incident_status_new, ':arrows_counterclockwise:') }} Status Change:* {{ incident_status_old }} → {{ incident_status_new }}",
"title": "{% set status_emojis = {'Closed': '✅', 'Stable': '🛡️', 'Active': '🔥'} %}{{ status_emojis.get(incident_status_new, '🔄') }} Status Change",
"text": "{{ incident_status_old }} → {{ incident_status_new }}",
}

INCIDENT_TYPE_CHANGE = {"title": "*:label: Incident Type Change:* {{ incident_type_old }} → {{ incident_type_new }}"}
INCIDENT_TYPE_CHANGE = {
"title": "🏷️ Incident Type Change",
"text": "{{ incident_type_old }} → {{ incident_type_new }}",
}

INCIDENT_SEVERITY_CHANGE = {
"title": "*{% if incident_severity_old.view_order < incident_severity_new.view_order %}:arrow_up:{% elif incident_severity_old.view_order > incident_severity_new.view_order %}:arrow_down:{% else %}:left_right_arrow:{% endif %} Severity Change:* {{ incident_severity_old.name }} → {{ incident_severity_new.name }}",
"title": "{% if incident_severity_old.view_order < incident_severity_new.view_order %}⬆️{% elif incident_severity_old.view_order > incident_severity_new.view_order %}⬇️{% else %}↔️{% endif %} Severity Change",
"text": "{{ incident_severity_old.name }} → {{ incident_severity_new.name }}",
}

INCIDENT_PRIORITY_CHANGE = {
"title": "*{% if incident_priority_old.view_order < incident_priority_new.view_order %}:arrow_up:{% elif incident_priority_old.view_order > incident_priority_new.view_order %}:arrow_down:{% else %}:left_right_arrow:{% endif %} Priority Change:* {{ incident_priority_old.name }} → {{ incident_priority_new.name }}",
"title": "{% if incident_priority_old.view_order < incident_priority_new.view_order %}⬆️{% elif incident_priority_old.view_order > incident_priority_new.view_order %}⬇️{% else %}↔️{% endif %} Priority Change",
"text": "{{ incident_priority_old.name }} → {{ incident_priority_new.name }}",
}

INCIDENT_PARTICIPANT_SUGGESTED_READING_ITEM = {
Expand Down Expand Up @@ -853,7 +867,8 @@ class MessageType(DispatchEnum):
}

CASE_ASSIGNEE = {
"title": ":female-detective: Assignee: <{{assignee_weblink}}|{{assignee_fullname}}, {{assignee_team}}>",
"title": "🕵️‍♀️ Assignee - {{assignee_fullname}}, {{assignee_team}}",
"title_link": "{{assignee_weblink}}",
"text": CASE_ASSIGNEE_DESCRIPTION,
}

Expand Down
22 changes: 15 additions & 7 deletions src/dispatch/plugins/dispatch_slack/messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,17 +190,25 @@ def build_unexpected_error_message(guid: str) -> str:
def format_default_text(item: dict):
"""Creates the correct Slack text string based on the item context."""
if item.get("title_link"):
text_part = f"\n{item['text']}" if item.get('text') else ""
return f"*<{item['title_link']}|{item['title']}>*{text_part}"
return f"*<{item['title_link']}|{item['title']}>*\n{item['text']}"
if item.get("datetime"):
return f"*{item['title']}*\n <!date^{int(item['datetime'].timestamp())}^ {{date}} | {item['datetime']}"
if item.get("title"):
text_part = f"\n{item['text']}" if item.get('text') else ""
# Check if title already has formatting (contains asterisks)
if '*' in item['title']:
return f"{item['title']}{text_part}"
# Titles that should be combined on a single line with ": "
single_line_titles = {
"📝 Title",
}

if item.get('text'):
# Check if this title should be on a single line or text contains → (e.g. a state transition)
if item['title'] in single_line_titles or '→' in item['text']:
text_part = f": {item['text']}"
else:
text_part = f"\n{item['text']}"
else:
return f"*{item['title']}*{text_part}"
text_part = ""

return f"*{item['title']}*{text_part}"
return item.get("text", "")


Expand Down
2 changes: 1 addition & 1 deletion src/dispatch/plugins/dispatch_slack/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ def create_genai_message_metadata_blocks(
message = json_to_slack_format(message)

# Truncate the text if it exceeds Block Kit's maximum length of 3000 characters
text = f":magic_wand: *{title}*\n\n{message}"
text = f"🪄 *{title}*\n\n{message}"
text = f"{text[:2997]}..." if len(text) > 3000 else text
blocks.append(
Section(text=text),
Expand Down
30 changes: 15 additions & 15 deletions tests/ai/test_ai_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def get_plugin_side_effect(db_session, plugin_type, project_id):
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand Down Expand Up @@ -152,7 +152,7 @@ def get_plugin_side_effect(db_session, plugin_type, project_id):
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand Down Expand Up @@ -194,7 +194,7 @@ def test_generate_read_in_summary_cache_hit(
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand Down Expand Up @@ -248,7 +248,7 @@ def get_plugin_side_effect(db_session, plugin_type, project_id):
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand All @@ -275,7 +275,7 @@ def test_generate_read_in_summary_no_ai_plugin(self, session, mock_subject, mock
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand Down Expand Up @@ -313,7 +313,7 @@ def test_generate_read_in_summary_no_conversation_plugin(
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand Down Expand Up @@ -361,7 +361,7 @@ def get_plugin_side_effect(db_session, plugin_type, project_id):
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand Down Expand Up @@ -412,7 +412,7 @@ def get_plugin_side_effect(db_session, plugin_type, project_id):
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand Down Expand Up @@ -442,7 +442,7 @@ def test_generate_read_in_summary_event_query_incident(
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand All @@ -465,7 +465,7 @@ def test_generate_read_in_summary_event_query_case(self, session, mock_subject,
subject=mock_subject,
project=mock_project,
channel_id="test-channel",
important_reaction=":white_check_mark:",
important_reaction="",
participant_email="test@example.com",
)

Expand Down Expand Up @@ -541,7 +541,7 @@ def get_plugin_side_effect(db_session, plugin_type, project_id):
db_session=session,
incident=mock_incident,
project=mock_project,
important_reaction=":fire:",
important_reaction="🔥",
)

# Assertions
Expand Down Expand Up @@ -579,7 +579,7 @@ def test_generate_tactical_report_no_ai_plugin(self, session, mock_incident, moc
db_session=session,
incident=mock_incident,
project=mock_project,
important_reaction=":fire:",
important_reaction="🔥",
)
print(type(result))
assert isinstance(result, TacticalReportResponse)
Expand All @@ -606,7 +606,7 @@ def test_generate_tactical_report_no_conversation_plugin(
db_session=session,
incident=mock_incident,
project=mock_project,
important_reaction=":fire:",
important_reaction="🔥",
)

assert isinstance(result, TacticalReportResponse)
Expand Down Expand Up @@ -642,7 +642,7 @@ def get_plugin_side_effect(db_session, plugin_type, project_id):
db_session=session,
incident=mock_incident,
project=mock_project,
important_reaction=":fire:",
important_reaction="🔥",
)

assert isinstance(result, TacticalReportResponse)
Expand Down Expand Up @@ -681,7 +681,7 @@ def get_plugin_side_effect(db_session, plugin_type, project_id):
db_session=session,
incident=mock_incident,
project=mock_project,
important_reaction=":fire:",
important_reaction="🔥",
)

assert isinstance(result, TacticalReportResponse)
Expand Down
Loading