Skip to content

Commit

Permalink
Update signal instance message (#3083)
Browse files Browse the repository at this point in the history
* Update signal instance message

* simulate overflowed field text

* add context message

---------

Co-authored-by: kevgliss <[email protected]>
  • Loading branch information
wssheldon and kevgliss authored Mar 10, 2023
1 parent 51e0462 commit a1d6a41
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/dispatch/case/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def create_conversation(case: Case, db_session: SessionLocal):
db_session=db_session, project_id=case.project.id, plugin_type="conversation"
)
conversation = plugin.instance.create_threaded(
case=case, conversation_id=case.case_type.conversation_target
case=case, conversation_id=case.case_type.conversation_target, db_session=db_session
)
conversation.update({"resource_type": plugin.plugin.slug, "resource_id": conversation["id"]})

Expand Down
8 changes: 4 additions & 4 deletions src/dispatch/entity/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,14 @@ def delete(*, db_session, entity_id: int):
db_session.commit()


def get_cases_with_entity(db: Session, entity_id: int, days_back: int) -> list[Case]:
def get_cases_with_entity(db_session: Session, entity_id: int, days_back: int) -> list[Case]:
"""Searches for cases with the same entity within a given timeframe."""
# Calculate the datetime for the start of the search window
start_date = datetime.utcnow() - timedelta(days=days_back)

# Query for signal instances containing the entity within the search window
cases = (
db.query(Case)
db_session.query(Case)
.join(Case.signal_instances)
.join(SignalInstance.entities)
.filter(Entity.id == entity_id, SignalInstance.created_at >= start_date)
Expand All @@ -146,15 +146,15 @@ def get_cases_with_entity(db: Session, entity_id: int, days_back: int) -> list[C


def get_signal_instances_with_entity(
db: Session, entity_id: int, days_back: int
db_session: Session, entity_id: int, days_back: int
) -> list[SignalInstance]:
"""Searches for signal instances with the same entity within a given timeframe."""
# Calculate the datetime for the start of the search window
start_date = datetime.utcnow() - timedelta(days=days_back)

# Query for signal instances containing the entity within the search window
signal_instances = (
db.query(SignalInstance)
db_session.query(SignalInstance)
.options(joinedload(SignalInstance.signal))
.join(SignalInstance.entities)
.filter(SignalInstance.created_at >= start_date, Entity.id == entity_id)
Expand Down
4 changes: 2 additions & 2 deletions src/dispatch/entity/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def count_cases_with_entity(
days_back: int = 7,
entity_id: PrimaryKey,
):
cases = get_cases_with_entity(db=db_session, entity_id=entity_id, days_back=days_back)
cases = get_cases_with_entity(db_session=db_session, entity_id=entity_id, days_back=days_back)
return {"cases": cases}


Expand All @@ -86,6 +86,6 @@ def get_signal_instances_by_entity(
entity_id: PrimaryKey,
):
instances = get_signal_instances_with_entity(
db=db_session, entity_id=entity_id, days_back=days_back
db_session=db_session, entity_id=entity_id, days_back=days_back
)
return {"instances": instances}
86 changes: 73 additions & 13 deletions src/dispatch/plugins/dispatch_slack/case/messages.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from collections import defaultdict, namedtuple
from typing import List

from blockkit import Actions, Button, Context, Message, Section, Divider, Overflow, PlainOption
from sqlalchemy.orm import Session

from dispatch.config import DISPATCH_UI_URL
from dispatch.case.enums import CaseStatus
from dispatch.case.models import Case
from dispatch.entity import service as entity_service
from dispatch.plugins.dispatch_slack.models import SubjectMetadata, CaseSubjects, SignalSubjects
from dispatch.plugins.dispatch_slack.case.enums import (
CaseNotificationActions,
SignalNotificationActions,
)
from collections import defaultdict


def create_case_message(case: Case, channel_id: str):
Expand Down Expand Up @@ -122,7 +125,7 @@ def create_case_message(case: Case, channel_id: str):
return Message(blocks=blocks).build()["blocks"]


def create_signal_messages(case: Case, channel_id: str) -> List[Message]:
def create_signal_messages(case: Case, channel_id: str, db_session: Session) -> List[Message]:
"""Creates the signal instance message."""
messages = []

Expand All @@ -137,34 +140,91 @@ def create_signal_messages(case: Case, channel_id: str) -> List[Message]:

signal_metadata_blocks = [
Section(
text=f"*Signal Entities* - {instance.id}",
accessory=Button(
text="View Raw",
action_id=SignalNotificationActions.view,
value=button_metadata,
),
text=f"*{instance.signal.name}* - {instance.signal.variant}",
),
Actions(
elements=[
Button(
text="View Raw Data",
action_id=SignalNotificationActions.view,
value=button_metadata,
),
Button(
text="Snooze",
action_id=SignalNotificationActions.snooze,
style="primary",
value=button_metadata,
)
),
]
),
Section(text="*Entities*"),
Divider(),
]

# group entities by entity type
if not instance.entities:
signal_metadata_blocks.append(
Section(
text="No entities found.",
),
)
EntityGroup = namedtuple(
"EntityGroup", ["value", "related_instance_count", "related_case_count"]
)
entity_groups = defaultdict(list)
for e in instance.entities:
entity_groups[e.entity_type.name].append(e.value)
related_instances = entity_service.get_signal_instances_with_entity(
db_session=db_session, entity_id=e.id, days_back=14
)
related_instance_count = len(related_instances)

related_cases = entity_service.get_cases_with_entity(
db_session=db_session, entity_id=e.id, days_back=14
)
related_case_count = len(related_cases)
entity_groups[e.entity_type.name].append(
EntityGroup(
value=e.value,
related_instance_count=related_instance_count,
related_case_count=related_case_count,
)
)
for k, v in entity_groups.items():
if v:
signal_metadata_blocks.append(Section(text=f"*{k}*", fields=v))
related_instance_count = v[0].related_instance_count
match related_instance_count:
case 0:
signal_message = "First time this entity has been seen in a signal."
case 1:
signal_message = f"Seen in *{related_instance_count}* other signal."
case _:
signal_message = f"Seen in *{related_instance_count}* other signals."

related_case_count = v[0].related_case_count
match related_case_count:
case 0:
case_message = "First time this entity has been seen in a case."
case 1:
case_message = f"Seen in *{related_instance_count}* other case."
case _:
case_message = f"Seen in *{related_instance_count}* other cases."

# dynamically allocate space for the entity type name and entity type values
entity_type_name_length = len(k)
entity_type_value_length = len(", ".join(item.value for item in v))
entity_type_name_spaces = " " * (55 - entity_type_name_length)
entity_type_value_spaces = " " * (50 - entity_type_value_length)

# Threaded messages do not overflow text fields, so we hack together the same UI with spaces
signal_metadata_blocks.append(
Context(
elements=[
f"*{k}*{entity_type_name_spaces}{signal_message}\n`{', '.join(item.value for item in v)}`{entity_type_value_spaces}{case_message}"
]
),
)
signal_metadata_blocks.append(Divider())

signal_metadata_blocks.append(
Context(elements=["Correlation is based on two weeks of signal data."]),
)
messages.append(Message(blocks=signal_metadata_blocks[:50]).build()["blocks"])
return messages
7 changes: 5 additions & 2 deletions src/dispatch/plugins/dispatch_slack/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from blockkit import Message
from joblib import Memory
from sqlalchemy.orm import Session

from dispatch.case.models import Case
from dispatch.conversation.enums import ConversationCommands
Expand Down Expand Up @@ -77,7 +78,7 @@ def create(self, name: str):
client = create_slack_client(self.configuration)
return create_conversation(client, name, self.configuration.private_channels)

def create_threaded(self, case: Case, conversation_id: str):
def create_threaded(self, case: Case, conversation_id: str, db_session: Session):
"""Creates a new threaded conversation."""
client = create_slack_client(self.configuration)
blocks = create_case_message(case=case, channel_id=conversation_id)
Expand All @@ -89,7 +90,9 @@ def create_threaded(self, case: Case, conversation_id: str):
ts=response["timestamp"],
)
if case.signal_instances:
messages = create_signal_messages(case=case, channel_id=conversation_id)
messages = create_signal_messages(
case=case, channel_id=conversation_id, db_session=db_session
)
for m in messages:
send_message(
client=client,
Expand Down

0 comments on commit a1d6a41

Please sign in to comment.