1
+ from collections import defaultdict , namedtuple
1
2
from typing import List
3
+
2
4
from blockkit import Actions , Button , Context , Message , Section , Divider , Overflow , PlainOption
5
+ from sqlalchemy .orm import Session
3
6
4
7
from dispatch .config import DISPATCH_UI_URL
5
8
from dispatch .case .enums import CaseStatus
6
9
from dispatch .case .models import Case
10
+ from dispatch .entity import service as entity_service
7
11
from dispatch .plugins .dispatch_slack .models import SubjectMetadata , CaseSubjects , SignalSubjects
8
12
from dispatch .plugins .dispatch_slack .case .enums import (
9
13
CaseNotificationActions ,
10
14
SignalNotificationActions ,
11
15
)
12
- from collections import defaultdict
13
16
14
17
15
18
def create_case_message (case : Case , channel_id : str ):
@@ -122,7 +125,7 @@ def create_case_message(case: Case, channel_id: str):
122
125
return Message (blocks = blocks ).build ()["blocks" ]
123
126
124
127
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 ]:
126
129
"""Creates the signal instance message."""
127
130
messages = []
128
131
@@ -137,34 +140,91 @@ def create_signal_messages(case: Case, channel_id: str) -> List[Message]:
137
140
138
141
signal_metadata_blocks = [
139
142
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 } " ,
146
144
),
147
145
Actions (
148
146
elements = [
147
+ Button (
148
+ text = "View Raw Data" ,
149
+ action_id = SignalNotificationActions .view ,
150
+ value = button_metadata ,
151
+ ),
149
152
Button (
150
153
text = "Snooze" ,
151
154
action_id = SignalNotificationActions .snooze ,
152
- style = "primary" ,
153
155
value = button_metadata ,
154
- )
156
+ ),
155
157
]
156
158
),
159
+ Section (text = "*Entities*" ),
160
+ Divider (),
157
161
]
158
162
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
+ )
160
172
entity_groups = defaultdict (list )
161
173
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 )
163
178
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
+ )
164
190
for k , v in entity_groups .items ():
165
191
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
+ )
167
224
signal_metadata_blocks .append (Divider ())
168
225
226
+ signal_metadata_blocks .append (
227
+ Context (elements = ["Correlation is based on two weeks of signal data." ]),
228
+ )
169
229
messages .append (Message (blocks = signal_metadata_blocks [:50 ]).build ()["blocks" ])
170
230
return messages
0 commit comments