From 2982a3560ce7747a744fac0b643f074a7e19280d Mon Sep 17 00:00:00 2001
From: Marc Vilanova <39573146+mvilanova@users.noreply.github.com>
Date: Thu, 3 Oct 2024 19:07:34 -0700
Subject: [PATCH] Improves error handling, logging, and messaging in Slack
plugin (#5288)
* Improves error handling, logging, and messaging in Slack plugin
* docstring
* comment
* prints
* print
* removes check
---
.../plugins/dispatch_slack/case/messages.py | 53 ++++++++++++-------
src/dispatch/plugins/dispatch_slack/plugin.py | 11 ++--
2 files changed, 41 insertions(+), 23 deletions(-)
diff --git a/src/dispatch/plugins/dispatch_slack/case/messages.py b/src/dispatch/plugins/dispatch_slack/case/messages.py
index aedf77be8f3c..8fe58b21eaa3 100644
--- a/src/dispatch/plugins/dispatch_slack/case/messages.py
+++ b/src/dispatch/plugins/dispatch_slack/case/messages.py
@@ -14,6 +14,7 @@
from slack_sdk.web.client import WebClient
from sqlalchemy.orm import Session
+from dispatch.case import service as case_service
from dispatch.case.enums import CaseResolutionReason, CaseStatus
from dispatch.case.models import Case
from dispatch.config import DISPATCH_UI_URL
@@ -212,13 +213,13 @@ def create_signal_message(case_id: int, channel_id: str, db_session: Session) ->
instances = signal_service.get_instances_in_case(db_session=db_session, case_id=case_id)
(first_instance_id, first_instance_signal) = instances.first()
- organization_slug = first_instance_signal.project.organization.slug
+ case = case_service.get(db_session=db_session, case_id=case_id)
# we create the signal metadata blocks
signal_metadata_blocks = [
Section(text="*Alerts*"),
Section(
- text=f"We observed <{DISPATCH_UI_URL}/{organization_slug}/cases/{first_instance_signal.case.name}/signal/{first_instance_signal.id}|{instances.count()} alerts> in this case. The first alert for this case can be seen below."
+ text=f"We observed <{DISPATCH_UI_URL}/{first_instance_signal.project.organization.slug}/cases/{case.name}/signal/{first_instance_id}|{instances.count()} alerts> in this case. The first alert for this case can be seen below."
),
]
@@ -226,7 +227,7 @@ def create_signal_message(case_id: int, channel_id: str, db_session: Session) ->
def create_action_buttons_message(
- case_id: int, channel_id: str, db_session: Session
+ case: Case, channel_id: str, db_session: Session
) -> list[Message]:
"""
Creates a message with action buttons for a given case.
@@ -243,7 +244,7 @@ def create_action_buttons_message(
list[Message]: A list of Message objects representing the structure of the Slack messages.
"""
# we fetch the first instance to get the organization slug and project id
- instances = signal_service.get_instances_in_case(db_session=db_session, case_id=case_id)
+ instances = signal_service.get_instances_in_case(db_session=db_session, case_id=case.id)
(first_instance_id, first_instance_signal) = instances.first()
organization_slug = first_instance_signal.project.organization.slug
@@ -321,16 +322,22 @@ def create_genai_signal_analysis_message(
"""
signal_metadata_blocks: list[Block] = []
+ # we fetch the first instance id and signal
(first_instance_id, first_instance_signal) = signal_service.get_instances_in_case(
db_session=db_session, case_id=case.id
).first()
- if not first_instance_id or not first_instance_signal:
- message = "Unable to generate GenAI signal analysis. No signal instances found."
+ signal_instance = signal_service.get_signal_instance(
+ db_session=db_session, signal_instance_id=first_instance_id
+ )
+
+ # we check if GenAI is enabled for the detection
+ if not signal_instance.signal.genai_enabled:
+ message = "Unable to generate GenAI signal analysis. GenAI feature not enabled for this detection."
log.warning(message)
return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)
- # Fetch related cases
+ # we fetch related cases
related_cases = []
for resolution_reason in CaseResolutionReason:
related_cases.extend(
@@ -343,7 +350,7 @@ def create_genai_signal_analysis_message(
.filter(Case.id != case.id)
)
- # Prepare historical context
+ # we prepare historical context
historical_context = []
for related_case in related_cases:
historical_context.append("")
@@ -353,33 +360,35 @@ def create_genai_signal_analysis_message(
f"{related_case.resolution_reason}"
)
- # Fetch Slack messages for the related case
+ # we fetch Slack messages for the related case
if related_case.conversation and related_case.conversation.channel_id:
try:
- # Fetch threaded messages
+ # we fetch threaded messages
thread_messages = client.conversations_replies(
channel=related_case.conversation.channel_id,
ts=related_case.conversation.thread_id,
)
- # Add relevant messages to the context (e.g., first 5 messages)
- for message in thread_messages["messages"][:5]:
+ # we add relevant messages to the context (e.g., first 10 messages)
+ for message in thread_messages["messages"][:10]:
historical_context.append(f"{message['text']}")
except SlackApiError as e:
- log.error(f"Error fetching Slack messages for case {related_case.name}: {e}")
+ log.error(
+ f"Unable to generate GenAI signal analysis. Error fetching Slack messages for case {related_case.name}: {e}"
+ )
+ message = "Unable to generate GenAI signal analysis. Error fetching Slack messages."
+ return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)
historical_context.append("")
historical_context_str = "\n".join(historical_context)
- signal_instance = signal_service.get_signal_instance(
- db_session=db_session, signal_instance_id=first_instance_id
- )
-
+ # we fetch the GenAI plugin
genai_plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=case.project.id, plugin_type="artificial-intelligence"
)
+ # we check if the GenAI plugin is enabled
if not genai_plugin:
message = (
"Unable to generate GenAI signal analysis. No artificial-intelligence plugin enabled."
@@ -387,11 +396,13 @@ def create_genai_signal_analysis_message(
log.warning(message)
return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)
+ # we check if the GenAI plugin has a prompt
if not signal_instance.signal.genai_prompt:
message = f"Unable to generate GenAI signal analysis. No GenAI prompt defined for {signal_instance.signal.name}"
log.warning(message)
return create_genai_signal_message_metadata_blocks(signal_metadata_blocks, message)
+ # we generate the analysis
response = genai_plugin.instance.chat_completion(
prompt=f"""
@@ -403,17 +414,19 @@ def create_genai_signal_analysis_message(
{str(signal_instance.raw)}
+
+ {signal_instance.signal.runbook}
+
+
{historical_context_str}
-
- {first_instance_signal.runbook}
-
"""
)
message = response["choices"][0]["message"]["content"]
+ # we check if the response is empty
if not message:
message = "Unable to generate GenAI signal analysis. We received an empty response from the artificial-intelligence plugin."
log.warning(message)
diff --git a/src/dispatch/plugins/dispatch_slack/plugin.py b/src/dispatch/plugins/dispatch_slack/plugin.py
index 824dfc32e438..c4ece692e90a 100644
--- a/src/dispatch/plugins/dispatch_slack/plugin.py
+++ b/src/dispatch/plugins/dispatch_slack/plugin.py
@@ -93,8 +93,11 @@ def create_threaded(self, case: Case, conversation_id: str, db_session: Session)
client = create_slack_client(self.configuration)
blocks = create_case_message(case=case, channel_id=conversation_id)
response = send_message(client=client, conversation_id=conversation_id, blocks=blocks)
+ response_timestamp = response["timestamp"]
if case.signal_instances:
+ signal_response = None
+
# we try to generate a GenAI signal analysis message
try:
if message := create_genai_signal_analysis_message(
@@ -106,13 +109,15 @@ def create_threaded(self, case: Case, conversation_id: str, db_session: Session)
signal_response = send_message(
client=client,
conversation_id=conversation_id,
- ts=response["timestamp"],
+ ts=response_timestamp,
blocks=message,
)
except Exception as e:
logger.exception(f"Error generating GenAI signal analysis message: {e}")
- case.signal_thread_ts = signal_response.get("timestamp")
+ case.signal_thread_ts = (
+ signal_response.get("timestamp") if signal_response else response_timestamp
+ )
# we try to generate a signal message
try:
@@ -151,7 +156,7 @@ def create_threaded(self, case: Case, conversation_id: str, db_session: Session)
# we try to generate action buttons
try:
message = create_action_buttons_message(
- case_id=case.id, channel_id=conversation_id, db_session=db_session
+ case=case, channel_id=conversation_id, db_session=db_session
)
send_message(
client=client,