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: 4 additions & 0 deletions src/dispatch/case/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
send_case_rating_feedback_message,
send_case_update_notifications,
send_event_paging_message,
send_event_update_prompt_reminder
)
from .models import Case
from .service import get
Expand Down Expand Up @@ -309,6 +310,9 @@ def case_new_create_flow(

send_event_paging_message(case, db_session, oncall_name)

# send reminder to assignee to update the security event
send_event_update_prompt_reminder(case, db_session)

if case and case.case_type.auto_close:
# we transition the case to the closed state if its case type has auto close enabled
case_auto_close_flow(case=case, db_session=db_session)
Expand Down
53 changes: 53 additions & 0 deletions src/dispatch/case/messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
from dispatch.config import DISPATCH_UI_URL
from dispatch.email_templates.models import EmailTemplates
from dispatch.plugin import service as plugin_service
from dispatch.plugins.dispatch_slack.models import SubjectMetadata
from dispatch.plugins.dispatch_slack.case.enums import CaseNotificationActions
from dispatch.event import service as event_service
from dispatch.notification import service as notification_service

Expand Down Expand Up @@ -377,6 +379,57 @@ def send_case_welcome_participant_message(
log.debug(f"Welcome ephemeral message sent to {participant_email}.")


def send_event_update_prompt_reminder(case: Case, db_session: Session) -> None:
"""
Sends an ephemeral message to the assignee reminding them to update the visibility, title, priority
"""
message_text = "Event Triage Reminder"

plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=case.project.id, plugin_type="conversation"
)
if plugin is None:
log.warning("Event update prompt message not sent. No conversation plugin enabled.")
return
if case.assignee is None:
log.warning(f"Event update prompt message not sent. No assignee for {case.name}.")
return

button_metadata = SubjectMetadata(
type="case",
organization_slug=case.project.organization.slug,
id=case.id,
).json()

plugin.instance.send_ephemeral(
conversation_id=case.conversation.channel_id,
user=case.assignee.individual.email,
text=message_text,
blocks=[
{
"type": "section",
"text": {
"type": "plain_text",
"text": f"Update the title, priority, case type and visibility during triage of this security event.", # noqa
},
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "Update Case"},
"action_id": CaseNotificationActions.update,
"style": "primary",
"value": button_metadata
}
],
},
],
)

log.debug(f"Security Event update reminder sent to {case.assignee.individual.email}.")

def send_event_paging_message(case: Case, db_session: Session, oncall_name: str) -> None:
"""
Sends a message to the case conversation channel to notify the reporter that they can engage
Expand Down
1 change: 1 addition & 0 deletions src/dispatch/conversation/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ConversationCommands(DispatchEnum):
report_incident = "report-incident"
tactical_report = "tactical-report"
update_incident = "update-incident"
escalate_case = "escalate-case"


class ConversationButtonActions(DispatchEnum):
Expand Down
1 change: 1 addition & 0 deletions src/dispatch/plugins/dispatch_slack/case/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class CaseNotificationActions(DispatchEnum):
resolve = "case-notification-resolve"
triage = "case-notification-triage"
user_mfa = "case-notification-user-mfa"
update = "case-update"


class CasePaginateActions(DispatchEnum):
Expand Down
36 changes: 34 additions & 2 deletions src/dispatch/plugins/dispatch_slack/case/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
case_resolution_reason_select,
case_status_select,
case_type_select,
case_visibility_select,
description_input,
entity_select,
extension_request_checkbox,
Expand Down Expand Up @@ -265,7 +266,11 @@ def handle_update_case_command(
Context(
elements=[
MarkdownText(
text=f"Note: Cases cannot be escalated here. Please use the `{context['config'].slack_command_escalate_case}` slash command."
text=(
"Note: Cases cannot be escalated here. Please use the "
f"{SlackConversationConfiguration.model_json_schema()['properties']['slack_command_escalate_case']['default']} "
"slash command."
)
)
]
),
Expand All @@ -280,6 +285,9 @@ def handle_update_case_command(
project_id=case.project.id,
optional=True,
),
case_visibility_select(
initial_option={"text": case.visibility, "value": case.visibility},
)
]

modal = Modal(
Expand Down Expand Up @@ -1639,6 +1647,26 @@ def create_channel_button_click(
client.views_open(trigger_id=body["trigger_id"], view=modal)


@app.action(
CaseNotificationActions.update,
middleware=[button_context_middleware, db_middleware, user_middleware],
)
def update_case_button_click(
ack: Ack,
body: dict,
client: WebClient,
context: BoltContext,
db_session: Session,
):
return handle_update_case_command(
ack=ack,
body=body,
client=client,
context=context,
db_session=db_session,
)


@app.action(
CaseNotificationActions.user_mfa,
middleware=[button_context_middleware, db_middleware, user_middleware],
Expand Down Expand Up @@ -2007,6 +2035,10 @@ def handle_edit_submission_event(
if form_data.get(DefaultBlockIds.case_type_select):
case_type = {"name": form_data[DefaultBlockIds.case_type_select]["name"]}

case_visibility = case.visibility
if form_data.get(DefaultBlockIds.case_visibility_select):
case_visibility = form_data[DefaultBlockIds.case_visibility_select]["value"]

assignee_email = None
if form_data.get(DefaultBlockIds.case_assignee_select):
assignee_email = client.users_info(
Expand All @@ -2023,7 +2055,7 @@ def handle_edit_submission_event(
resolution=form_data[DefaultBlockIds.resolution_input],
resolution_reason=resolution_reason,
status=form_data[DefaultBlockIds.case_status_select]["name"],
visibility=case.visibility,
visibility=case_visibility,
case_priority=case_priority,
case_type=case_type,
)
Expand Down
28 changes: 27 additions & 1 deletion src/dispatch/plugins/dispatch_slack/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from dispatch.case.severity import service as case_severity_service
from dispatch.case.type import service as case_type_service
from dispatch.entity import service as entity_service
from dispatch.enums import DispatchEnum
from dispatch.enums import DispatchEnum, Visibility
from dispatch.incident.enums import IncidentStatus
from dispatch.incident.priority import service as incident_priority_service
from dispatch.incident.severity import service as incident_severity_service
Expand Down Expand Up @@ -55,6 +55,7 @@ class DefaultBlockIds(DispatchEnum):
case_status_select = "case-status-select"
case_severity_select = "case-severity-select"
case_type_select = "case-type-select"
case_visibility_select = "case-visibility-select"
case_assignee_select = "case-assignee-select"

# entities
Expand Down Expand Up @@ -94,6 +95,7 @@ class DefaultActionIds(DispatchEnum):
case_status_select = "case-status-select"
case_severity_select = "case-severity-select"
case_type_select = "case-type-select"
case_visibility_select = "case-visibility-select"

# entities
entity_select = "entity-select"
Expand Down Expand Up @@ -684,6 +686,30 @@ def case_type_select(
)


def case_visibility_select(
action_id: str = DefaultActionIds.case_visibility_select,
block_id: str = DefaultBlockIds.case_visibility_select,
label: str = "Case Visibility",
initial_option: dict | None = None,
**kwargs,
):
"""Creates a case visibility select."""
visibility = [
{"text": Visibility.restricted, "value": Visibility.restricted},
{"text": Visibility.open, "value": Visibility.open}
]

return static_select_block(
placeholder="Select Visibility",
options=visibility,
initial_option=initial_option,
action_id=action_id,
block_id=block_id,
label=label,
**kwargs,
)


def entity_select(
signal_id: int,
db_session: Session,
Expand Down
1 change: 1 addition & 0 deletions src/dispatch/plugins/dispatch_slack/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ def get_command_name(self, command: str):
ConversationCommands.list_participants: self.configuration.slack_command_list_participants,
ConversationCommands.list_tasks: self.configuration.slack_command_list_tasks,
ConversationCommands.tactical_report: self.configuration.slack_command_report_tactical,
ConversationCommands.escalate_case: self.configuration.slack_command_escalate_case,
}
return command_mappings.get(command, [])

Expand Down
Loading