From 77521ac927e79b827e577eb1305235bb9bcf4b64 Mon Sep 17 00:00:00 2001 From: Avery Lee Date: Wed, 6 Nov 2024 11:45:18 -0800 Subject: [PATCH 1/3] Enables usage of welcome message template for creating case ephemeral welcome messages. These messages pop up when participants are automatically or manually added to the case channel. --- src/dispatch/case/flows.py | 33 +++- src/dispatch/case/messaging.py | 53 +++++- src/dispatch/conversation/flows.py | 13 +- src/dispatch/messaging/email/utils.py | 1 + src/dispatch/messaging/strings.py | 153 ++++++++++++++++-- .../plugins/dispatch_slack/case/messages.py | 36 ----- .../plugins/dispatch_slack/service.py | 2 + 7 files changed, 225 insertions(+), 66 deletions(-) diff --git a/src/dispatch/case/flows.py b/src/dispatch/case/flows.py index 0736474f4cfa..9d8cbcce9b10 100644 --- a/src/dispatch/case/flows.py +++ b/src/dispatch/case/flows.py @@ -10,6 +10,8 @@ from dispatch.conversation import flows as conversation_flows from dispatch.decorators import background_task from dispatch.document import flows as document_flows +from dispatch.email_templates import service as email_template_service +from dispatch.email_templates.enums import EmailTemplateTypes from dispatch.enums import DocumentResourceTypes, EventType, Visibility from dispatch.event import service as event_service from dispatch.group import flows as group_flows @@ -130,10 +132,19 @@ def case_add_or_reactivate_participant_flow( case, [participant.individual.email], db_session ) - # we send the welcome messages to the participant - send_case_welcome_participant_message( - participant_email=user_email, case=case, db_session=db_session - ) + # check to see if there is an override welcome message template + welcome_template = email_template_service.get_by_type( + db_session=db_session, + project_id=case.project_id, + email_template_type=EmailTemplateTypes.welcome, + ) + + send_case_welcome_participant_message( + participant_email=user_email, + case=case, + db_session=db_session, + welcome_template=welcome_template, + ) return participant @@ -1039,6 +1050,13 @@ def case_create_resources_flow( conversation_target=conversation_target, ) + # check to see if there is an override welcome message template + welcome_template = email_template_service.get_by_type( + db_session=db_session, + project_id=case.project_id, + email_template_type=EmailTemplateTypes.welcome, + ) + for user_email in set(individual_participants): send_participant_announcement_message( db_session=db_session, @@ -1046,6 +1064,13 @@ def case_create_resources_flow( subject=case, ) + send_case_welcome_participant_message( + participant_email=user_email, + case=case, + db_session=db_session, + welcome_template=welcome_template, + ) + event_service.log_case_event( db_session=db_session, source="Dispatch Core App", diff --git a/src/dispatch/case/messaging.py b/src/dispatch/case/messaging.py index d223a03d37b7..02143f19ff2e 100644 --- a/src/dispatch/case/messaging.py +++ b/src/dispatch/case/messaging.py @@ -7,9 +7,12 @@ import logging +from typing import Optional + from sqlalchemy.orm import Session from dispatch.database.core import resolve_attr +from dispatch.document import service as document_service from dispatch.case.models import Case, CaseRead from dispatch.messaging.strings import ( CASE_CLOSE_REMINDER, @@ -26,14 +29,13 @@ CASE_PRIORITY_CHANGE, CASE_CLOSED_RATING_FEEDBACK_NOTIFICATION, MessageType, + generate_welcome_message, ) from dispatch.config import DISPATCH_UI_URL +from dispatch.email_templates.models import EmailTemplates from dispatch.plugin import service as plugin_service from dispatch.event import service as event_service from dispatch.notification import service as notification_service -from dispatch.plugins.dispatch_slack.case.messages import ( - create_welcome_ephemeral_message_to_participant, -) from .enums import CaseStatus @@ -310,6 +312,7 @@ def send_case_welcome_participant_message( participant_email: str, case: Case, db_session: Session, + welcome_template: Optional[EmailTemplates] = None, ): if not case.dedicated_channel: return @@ -322,12 +325,52 @@ def send_case_welcome_participant_message( log.warning("Case participant welcome message not sent. No conversation plugin enabled.") return - welcome_message = create_welcome_ephemeral_message_to_participant(case=case) + # we send the ephemeral message + message_kwargs = { + "name": case.name, + "title": case.title, + "description": case.description, + "visibility": case.visibility, + "status": case.status, + "type": case.case_type.name, + "type_description": case.case_type.description, + "severity": case.case_severity.name, + "severity_description": case.case_severity.description, + "priority": case.case_priority.name, + "priority_description": case.case_priority.description, + "assignee_fullname": case.assignee.individual.name, + "assignee_team": case.assignee.team, + "assignee_weblink": case.assignee.individual.weblink, + "reporter_fullname": case.reporter.individual.name, + "reporter_team": case.reporter.team, + "reporter_weblink": case.reporter.individual.weblink, + "document_weblink": resolve_attr(case, "case_document.weblink"), + "storage_weblink": resolve_attr(case, "storage.weblink"), + "ticket_weblink": resolve_attr(case, "ticket.weblink"), + "conference_weblink": resolve_attr(case, "conference.weblink"), + "conference_challenge": resolve_attr(case, "conference.conference_challenge"), + } + faq_doc = document_service.get_incident_faq_document( + db_session=db_session, project_id=case.project_id + ) + if faq_doc: + message_kwargs.update({"faq_weblink": faq_doc.weblink}) + + conversation_reference = document_service.get_conversation_reference_document( + db_session=db_session, project_id=case.project_id + ) + if conversation_reference: + message_kwargs.update( + {"conversation_commands_reference_document_weblink": conversation_reference.weblink} + ) + plugin.instance.send_ephemeral( conversation_id=case.conversation.channel_id, user=participant_email, text=f"Welcome to {case.name}", - blocks=welcome_message, + message_template=generate_welcome_message(welcome_template, is_incident=False), + notification_type=MessageType.case_participant_welcome, + **message_kwargs, ) log.debug(f"Welcome ephemeral message sent to {participant_email}.") diff --git a/src/dispatch/conversation/flows.py b/src/dispatch/conversation/flows.py index 375450312f3e..6909cd64a117 100644 --- a/src/dispatch/conversation/flows.py +++ b/src/dispatch/conversation/flows.py @@ -462,11 +462,14 @@ def add_case_participants( return try: - plugin.instance.add_to_thread( - case.conversation.channel_id, - case.conversation.thread_id, - participant_emails, - ) + if case.has_thread: + plugin.instance.add_to_thread( + case.conversation.channel_id, + case.conversation.thread_id, + participant_emails, + ) + elif case.has_channel: + plugin.instance.add(case.conversation.channel_id, participant_emails) except Exception as e: event_service.log_case_event( db_session=db_session, diff --git a/src/dispatch/messaging/email/utils.py b/src/dispatch/messaging/email/utils.py index 19aed7dfebaa..81f2a96dbdfe 100644 --- a/src/dispatch/messaging/email/utils.py +++ b/src/dispatch/messaging/email/utils.py @@ -31,6 +31,7 @@ def get_template(message_type: MessageType, project_id: int): MessageType.case_notification: ("notification.mjml", None), MessageType.incident_participant_welcome: ("notification.mjml", None), MessageType.incident_tactical_report: ("tactical_report.mjml", None), + MessageType.case_participant_welcome: ("notification.mjml", None), MessageType.incident_task_reminder: ( "notification_list.mjml", INCIDENT_TASK_REMINDER_DESCRIPTION, diff --git a/src/dispatch/messaging/strings.py b/src/dispatch/messaging/strings.py index b98cfc7e26a9..6bd6821492e7 100644 --- a/src/dispatch/messaging/strings.py +++ b/src/dispatch/messaging/strings.py @@ -44,6 +44,7 @@ class MessageType(DispatchEnum): task_add_to_incident = "task-add-to-incident" case_rating_feedback = "case-rating-feedback" case_feedback_daily_report = "case-feedback-daily-report" + case_participant_welcome = "case-participant-welcome" INCIDENT_STATUS_DESCRIPTIONS = { @@ -118,6 +119,11 @@ class MessageType(DispatchEnum): "\n", " " ).strip() +CASE_REPORTER_DESCRIPTION = """ +The person who reported the case. Contact them if the report details need clarification.""".replace( + "\n", " " +).strip() + INCIDENT_REPORTER_DESCRIPTION = """ The person who reported the incident. Contact them if the report details need clarification.""".replace( "\n", " " @@ -157,12 +163,24 @@ class MessageType(DispatchEnum): "\n", " " ).strip() +CASE_CONVERSATION_REFERENCE_DOCUMENT_DESCRIPTION = """ +Document containing the list of slash commands available to the Assignee +and participants in the case conversation.""".replace( + "\n", " " +).strip() + INCIDENT_CONVERSATION_REFERENCE_DOCUMENT_DESCRIPTION = """ Document containing the list of slash commands available to the Incident Commander (IC) and participants in the incident conversation.""".replace( "\n", " " ).strip() +CASE_CONFERENCE_DESCRIPTION = """ +Video conference and phone bridge to be used throughout the case. Password: {{conference_challenge if conference_challenge else 'N/A'}} +""".replace( + "\n", "" +).strip() + INCIDENT_CONFERENCE_DESCRIPTION = """ Video conference and phone bridge to be used throughout the incident. Password: {{conference_challenge if conference_challenge else 'N/A'}} """.replace( @@ -197,6 +215,13 @@ class MessageType(DispatchEnum): "\n", " " ).strip() +CASE_FAQ_DOCUMENT_DESCRIPTION = """ +First time responding to a case? This +document answers common questions encountered when +helping us respond to a case.""".replace( + "\n", " " +).strip() + INCIDENT_FAQ_DOCUMENT_DESCRIPTION = """ First time responding to an incident? This document answers common questions encountered when @@ -246,6 +271,13 @@ class MessageType(DispatchEnum): "\n", " " ).strip() +CASE_PARTICIPANT_WELCOME_DESCRIPTION = """ +You\'ve been added to this case, because we think you may +be able to help resolve it. Please review the case details below and +reach out to the assignee if you have any questions.""".replace( + "\n", " " +).strip() + INCIDENT_PARTICIPANT_WELCOME_DESCRIPTION = """ You\'ve been added to this incident, because we think you may be able to help resolve it. Please review the incident details below and @@ -804,6 +836,24 @@ class MessageType(DispatchEnum): INCIDENT_STATUS, ] +CASE_DESCRIPTION = {"title": "Description", "text": "{{description}}"} + +CASE_VISIBILITY = { + "title": "Visibility - {{visibility}}", + "visibility_mapping": CASE_VISIBILITY_DESCRIPTIONS, +} + +CASE_TYPE = {"title": "Type - {{type}}", "text": "{{type_description}}"} + +CASE_SEVERITY = { + "title": "Severity - {{severity}}", + "text": "{{severity_description}}", +} + +CASE_PRIORITY = { + "title": "Priority - {{priority}}", + "text": "{{priority_description}}", +} CASE_CLOSE_REMINDER = [ { @@ -849,6 +899,48 @@ class MessageType(DispatchEnum): "text": CASE_ASSIGNEE_DESCRIPTION, } +CASE_CONFERENCE = { + "title": "Conference", + "title_link": "{{conference_weblink}}", + "text": CASE_CONFERENCE_DESCRIPTION, +} + +CASE_STORAGE = { + "title": "Storage", + "title_link": "{{storage_weblink}}", + "text": STORAGE_DESCRIPTION, +} + +CASE_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT = { + "title": "Incident Conversation Commands Reference Document", + "title_link": "{{conversation_commands_reference_document_weblink}}", + "text": CASE_CONVERSATION_REFERENCE_DOCUMENT_DESCRIPTION, +} + +CASE_INVESTIGATION_DOCUMENT = { + "title": "Investigation Document", + "title_link": "{{document_weblink}}", + "text": CASE_INVESTIGATION_DOCUMENT_DESCRIPTION, +} + +INCIDENT_REVIEW_DOCUMENT = { + "title": "Review Document", + "title_link": "{{review_document_weblink}}", + "text": INCIDENT_REVIEW_DOCUMENT_DESCRIPTION, +} + +CASE_FAQ_DOCUMENT = { + "title": "FAQ Document", + "title_link": "{{faq_weblink}}", + "text": CASE_FAQ_DOCUMENT_DESCRIPTION, +} + +CASE_PARTICIPANT_WELCOME = { + "title": "Welcome to {{name}}", + "title_link": "{{ticket_weblink}}", + "text": CASE_PARTICIPANT_WELCOME_DESCRIPTION, +} + CASE_NOTIFICATION_COMMON = [CASE_TITLE] CASE_NOTIFICATION = CASE_NOTIFICATION_COMMON.copy() @@ -864,6 +956,24 @@ class MessageType(DispatchEnum): ] ) +CASE_PARTICIPANT_WELCOME_MESSAGE = [ + CASE_PARTICIPANT_WELCOME, + CASE_TITLE, + CASE_DESCRIPTION, + CASE_VISIBILITY, + CASE_STATUS, + CASE_TYPE, + CASE_SEVERITY, + CASE_PRIORITY, + CASE_REPORTER, + CASE_ASSIGNEE, + CASE_INVESTIGATION_DOCUMENT, + CASE_STORAGE, + CASE_CONFERENCE, + CASE_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT, + CASE_FAQ_DOCUMENT, +] + INCIDENT_TASK_REMINDER = [ {"title": "Incident - {{ name }}", "text": "{{ title }}"}, @@ -1198,10 +1308,15 @@ def render_message_template(message_template: List[dict], **kwargs): return data -def generate_welcome_message(welcome_message: EmailTemplates) -> Optional[List[dict]]: +def generate_welcome_message( + welcome_message: EmailTemplates, is_incident: bool = True +) -> Optional[List[dict]]: """Generates the welcome message.""" if welcome_message is None: - return INCIDENT_PARTICIPANT_WELCOME_MESSAGE + if is_incident: + return INCIDENT_PARTICIPANT_WELCOME_MESSAGE + else: + return CASE_PARTICIPANT_WELCOME_MESSAGE participant_welcome = { "title": welcome_message.welcome_text, @@ -1210,20 +1325,26 @@ def generate_welcome_message(welcome_message: EmailTemplates) -> Optional[List[d } component_mapping = { - "Title": INCIDENT_TITLE, - "Description": INCIDENT_DESCRIPTION, - "Visibility": INCIDENT_VISIBILITY, - "Status": INCIDENT_STATUS, - "Type": INCIDENT_TYPE, - "Severity": INCIDENT_SEVERITY, - "Priority": INCIDENT_PRIORITY, - "Reporter": INCIDENT_REPORTER, - "Commander": INCIDENT_COMMANDER, - "Investigation Document": INCIDENT_INVESTIGATION_DOCUMENT, - "Storage": INCIDENT_STORAGE, - "Conference": INCIDENT_CONFERENCE, - "Slack Commands": INCIDENT_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT, - "FAQ Document": INCIDENT_FAQ_DOCUMENT, + "Title": INCIDENT_TITLE if is_incident else CASE_TITLE, + "Description": INCIDENT_DESCRIPTION if is_incident else CASE_DESCRIPTION, + "Visibility": INCIDENT_VISIBILITY if is_incident else CASE_VISIBILITY, + "Status": INCIDENT_STATUS if is_incident else CASE_STATUS, + "Type": INCIDENT_TYPE if is_incident else CASE_TYPE, + "Severity": INCIDENT_SEVERITY if is_incident else CASE_SEVERITY, + "Priority": INCIDENT_PRIORITY if is_incident else CASE_PRIORITY, + "Reporter": INCIDENT_REPORTER if is_incident else CASE_REPORTER, + "Commander": INCIDENT_COMMANDER if is_incident else CASE_ASSIGNEE, + "Investigation Document": ( + INCIDENT_INVESTIGATION_DOCUMENT if is_incident else CASE_INVESTIGATION_DOCUMENT + ), + "Storage": INCIDENT_STORAGE if is_incident else CASE_STORAGE, + "Conference": INCIDENT_CONFERENCE if is_incident else CASE_CONFERENCE, + "Slack Commands": ( + INCIDENT_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT + if is_incident + else CASE_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT + ), + "FAQ Document": INCIDENT_FAQ_DOCUMENT if is_incident else CASE_FAQ_DOCUMENT, } message = [participant_welcome] diff --git a/src/dispatch/plugins/dispatch_slack/case/messages.py b/src/dispatch/plugins/dispatch_slack/case/messages.py index 0b722a1001c6..13b464fb3788 100644 --- a/src/dispatch/plugins/dispatch_slack/case/messages.py +++ b/src/dispatch/plugins/dispatch_slack/case/messages.py @@ -524,42 +524,6 @@ def create_manual_engagement_message( return Message(blocks=blocks).build()["blocks"] -def create_welcome_ephemeral_message_to_participant(case: Case) -> list[Block]: - blocks = [ - Section( - text="You've been added to this case, because we think you may be able to help resolve it. Please, review the case details below and reach out to the case assignee if you have any questions.", - ), - Section( - text=f"*Title* \n {case.title}", - ), - Section( - text=f"*Description* \n {case.description}", - ), - Section( - text=f"*Visibility - {case.visibility}* \n {CASE_VISIBILITY_DESCRIPTIONS[case.visibility]}", - ), - Section( - text=f"*Status - {case.status}* \n {CASE_STATUS_DESCRIPTIONS[case.status]}", - ), - Section( - text=f"*Type - {case.case_type.name}* \n {case.case_type.description}", - ), - Section( - text=f"*Severity - {case.case_severity.name}* \n {case.case_severity.description}", - ), - Section( - text=f"*Priority - {case.case_priority.name}* \n {case.case_priority.description}", - ), - Section( - text=f"*Assignee - {case.assignee.individual.name}*", - ), - Section( - text=f"*Reporter - {case.reporter.individual.name}*", - ), - ] - return Message(blocks=blocks).build()["blocks"] - - def create_case_thread_migration_message(channel_weblink: str) -> list[Block]: blocks = [ Context( diff --git a/src/dispatch/plugins/dispatch_slack/service.py b/src/dispatch/plugins/dispatch_slack/service.py index 433023af5d84..d07d2fc367f8 100644 --- a/src/dispatch/plugins/dispatch_slack/service.py +++ b/src/dispatch/plugins/dispatch_slack/service.py @@ -391,6 +391,8 @@ def add_users_to_conversation(client: WebClient, conversation_id: str, user_ids: # that result in folks already existing in the channel. if e.response["error"] == SlackAPIErrorCode.USER_IN_CHANNEL: pass + elif e.response["error"] == SlackAPIErrorCode.ALREADY_IN_CHANNEL: + pass def get_message_permalink(client: WebClient, conversation_id: str, ts: str) -> str: From 4bcb878b0a5c94c8fe87bac239d63eab566a0528 Mon Sep 17 00:00:00 2001 From: Avery Lee Date: Wed, 6 Nov 2024 11:50:00 -0800 Subject: [PATCH 2/3] Remove unused imports. --- src/dispatch/plugins/dispatch_slack/case/messages.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dispatch/plugins/dispatch_slack/case/messages.py b/src/dispatch/plugins/dispatch_slack/case/messages.py index 13b464fb3788..aa9b1f9c2c45 100644 --- a/src/dispatch/plugins/dispatch_slack/case/messages.py +++ b/src/dispatch/plugins/dispatch_slack/case/messages.py @@ -18,7 +18,6 @@ from dispatch.case.enums import CaseStatus from dispatch.case.models import Case from dispatch.config import DISPATCH_UI_URL -from dispatch.messaging.strings import CASE_STATUS_DESCRIPTIONS, CASE_VISIBILITY_DESCRIPTIONS from dispatch.plugins.dispatch_slack.case.enums import ( CaseNotificationActions, SignalEngagementActions, From d8312156b29c68c50ba54861561d9ac005352902 Mon Sep 17 00:00:00 2001 From: Avery Lee Date: Tue, 19 Nov 2024 13:12:56 -0800 Subject: [PATCH 3/3] Remove duplicate vars --- src/dispatch/messaging/strings.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/dispatch/messaging/strings.py b/src/dispatch/messaging/strings.py index 6bd6821492e7..dfe6eda5678c 100644 --- a/src/dispatch/messaging/strings.py +++ b/src/dispatch/messaging/strings.py @@ -119,10 +119,6 @@ class MessageType(DispatchEnum): "\n", " " ).strip() -CASE_REPORTER_DESCRIPTION = """ -The person who reported the case. Contact them if the report details need clarification.""".replace( - "\n", " " -).strip() INCIDENT_REPORTER_DESCRIPTION = """ The person who reported the incident. Contact them if the report details need clarification.""".replace( @@ -923,11 +919,6 @@ class MessageType(DispatchEnum): "text": CASE_INVESTIGATION_DOCUMENT_DESCRIPTION, } -INCIDENT_REVIEW_DOCUMENT = { - "title": "Review Document", - "title_link": "{{review_document_weblink}}", - "text": INCIDENT_REVIEW_DOCUMENT_DESCRIPTION, -} CASE_FAQ_DOCUMENT = { "title": "FAQ Document",