Skip to content

Commit a1d6a41

Browse files
wssheldonkevgliss
andauthored
Update signal instance message (#3083)
* Update signal instance message * simulate overflowed field text * add context message --------- Co-authored-by: kevgliss <[email protected]>
1 parent 51e0462 commit a1d6a41

File tree

5 files changed

+85
-22
lines changed

5 files changed

+85
-22
lines changed

src/dispatch/case/flows.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def create_conversation(case: Case, db_session: SessionLocal):
152152
db_session=db_session, project_id=case.project.id, plugin_type="conversation"
153153
)
154154
conversation = plugin.instance.create_threaded(
155-
case=case, conversation_id=case.case_type.conversation_target
155+
case=case, conversation_id=case.case_type.conversation_target, db_session=db_session
156156
)
157157
conversation.update({"resource_type": plugin.plugin.slug, "resource_id": conversation["id"]})
158158

src/dispatch/entity/service.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,14 @@ def delete(*, db_session, entity_id: int):
129129
db_session.commit()
130130

131131

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

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

147147

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

155155
# Query for signal instances containing the entity within the search window
156156
signal_instances = (
157-
db.query(SignalInstance)
157+
db_session.query(SignalInstance)
158158
.options(joinedload(SignalInstance.signal))
159159
.join(SignalInstance.entities)
160160
.filter(SignalInstance.created_at >= start_date, Entity.id == entity_id)

src/dispatch/entity/views.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def count_cases_with_entity(
7474
days_back: int = 7,
7575
entity_id: PrimaryKey,
7676
):
77-
cases = get_cases_with_entity(db=db_session, entity_id=entity_id, days_back=days_back)
77+
cases = get_cases_with_entity(db_session=db_session, entity_id=entity_id, days_back=days_back)
7878
return {"cases": cases}
7979

8080

@@ -86,6 +86,6 @@ def get_signal_instances_by_entity(
8686
entity_id: PrimaryKey,
8787
):
8888
instances = get_signal_instances_with_entity(
89-
db=db_session, entity_id=entity_id, days_back=days_back
89+
db_session=db_session, entity_id=entity_id, days_back=days_back
9090
)
9191
return {"instances": instances}

src/dispatch/plugins/dispatch_slack/case/messages.py

+73-13
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
from collections import defaultdict, namedtuple
12
from typing import List
3+
24
from blockkit import Actions, Button, Context, Message, Section, Divider, Overflow, PlainOption
5+
from sqlalchemy.orm import Session
36

47
from dispatch.config import DISPATCH_UI_URL
58
from dispatch.case.enums import CaseStatus
69
from dispatch.case.models import Case
10+
from dispatch.entity import service as entity_service
711
from dispatch.plugins.dispatch_slack.models import SubjectMetadata, CaseSubjects, SignalSubjects
812
from dispatch.plugins.dispatch_slack.case.enums import (
913
CaseNotificationActions,
1014
SignalNotificationActions,
1115
)
12-
from collections import defaultdict
1316

1417

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

124127

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

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

138141
signal_metadata_blocks = [
139142
Section(
140-
text=f"*Signal Entities* - {instance.id}",
141-
accessory=Button(
142-
text="View Raw",
143-
action_id=SignalNotificationActions.view,
144-
value=button_metadata,
145-
),
143+
text=f"*{instance.signal.name}* - {instance.signal.variant}",
146144
),
147145
Actions(
148146
elements=[
147+
Button(
148+
text="View Raw Data",
149+
action_id=SignalNotificationActions.view,
150+
value=button_metadata,
151+
),
149152
Button(
150153
text="Snooze",
151154
action_id=SignalNotificationActions.snooze,
152-
style="primary",
153155
value=button_metadata,
154-
)
156+
),
155157
]
156158
),
159+
Section(text="*Entities*"),
160+
Divider(),
157161
]
158162

159-
# group entities by entity type
163+
if not instance.entities:
164+
signal_metadata_blocks.append(
165+
Section(
166+
text="No entities found.",
167+
),
168+
)
169+
EntityGroup = namedtuple(
170+
"EntityGroup", ["value", "related_instance_count", "related_case_count"]
171+
)
160172
entity_groups = defaultdict(list)
161173
for e in instance.entities:
162-
entity_groups[e.entity_type.name].append(e.value)
174+
related_instances = entity_service.get_signal_instances_with_entity(
175+
db_session=db_session, entity_id=e.id, days_back=14
176+
)
177+
related_instance_count = len(related_instances)
163178

179+
related_cases = entity_service.get_cases_with_entity(
180+
db_session=db_session, entity_id=e.id, days_back=14
181+
)
182+
related_case_count = len(related_cases)
183+
entity_groups[e.entity_type.name].append(
184+
EntityGroup(
185+
value=e.value,
186+
related_instance_count=related_instance_count,
187+
related_case_count=related_case_count,
188+
)
189+
)
164190
for k, v in entity_groups.items():
165191
if v:
166-
signal_metadata_blocks.append(Section(text=f"*{k}*", fields=v))
192+
related_instance_count = v[0].related_instance_count
193+
match related_instance_count:
194+
case 0:
195+
signal_message = "First time this entity has been seen in a signal."
196+
case 1:
197+
signal_message = f"Seen in *{related_instance_count}* other signal."
198+
case _:
199+
signal_message = f"Seen in *{related_instance_count}* other signals."
200+
201+
related_case_count = v[0].related_case_count
202+
match related_case_count:
203+
case 0:
204+
case_message = "First time this entity has been seen in a case."
205+
case 1:
206+
case_message = f"Seen in *{related_instance_count}* other case."
207+
case _:
208+
case_message = f"Seen in *{related_instance_count}* other cases."
209+
210+
# dynamically allocate space for the entity type name and entity type values
211+
entity_type_name_length = len(k)
212+
entity_type_value_length = len(", ".join(item.value for item in v))
213+
entity_type_name_spaces = " " * (55 - entity_type_name_length)
214+
entity_type_value_spaces = " " * (50 - entity_type_value_length)
215+
216+
# Threaded messages do not overflow text fields, so we hack together the same UI with spaces
217+
signal_metadata_blocks.append(
218+
Context(
219+
elements=[
220+
f"*{k}*{entity_type_name_spaces}{signal_message}\n`{', '.join(item.value for item in v)}`{entity_type_value_spaces}{case_message}"
221+
]
222+
),
223+
)
167224
signal_metadata_blocks.append(Divider())
168225

226+
signal_metadata_blocks.append(
227+
Context(elements=["Correlation is based on two weeks of signal data."]),
228+
)
169229
messages.append(Message(blocks=signal_metadata_blocks[:50]).build()["blocks"])
170230
return messages

src/dispatch/plugins/dispatch_slack/plugin.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from blockkit import Message
1414
from joblib import Memory
15+
from sqlalchemy.orm import Session
1516

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

80-
def create_threaded(self, case: Case, conversation_id: str):
81+
def create_threaded(self, case: Case, conversation_id: str, db_session: Session):
8182
"""Creates a new threaded conversation."""
8283
client = create_slack_client(self.configuration)
8384
blocks = create_case_message(case=case, channel_id=conversation_id)
@@ -89,7 +90,9 @@ def create_threaded(self, case: Case, conversation_id: str):
8990
ts=response["timestamp"],
9091
)
9192
if case.signal_instances:
92-
messages = create_signal_messages(case=case, channel_id=conversation_id)
93+
messages = create_signal_messages(
94+
case=case, channel_id=conversation_id, db_session=db_session
95+
)
9396
for m in messages:
9497
send_message(
9598
client=client,

0 commit comments

Comments
 (0)