7
7
8
8
9
9
class SlackMessageGenerator (generators .base .BaseMessageGenerator ):
10
+ # Slack has a limit of 3001 characters per message block
11
+ # We will use this constant to split the message blocks into smaller ones
12
+ SLACK_MAX_MESSAGE_BLOCK_SIZE = 3000
10
13
11
14
def scorecard_report (self , blueprint : str , scorecard : Dict [str , Any ], entities : list ):
12
15
blueprint_plural = utils .convert_to_plural (blueprint ).title ()
@@ -233,14 +236,10 @@ def scorecard_reminder(self,
233
236
"text" : f"*⚠️ { number_of_entities_didnt_pass_all_rules } { blueprint_plural } with unmet rules*"
234
237
}
235
238
},
236
- {
237
- "type" : "section" ,
238
- "text" : {
239
- "type" : "mrkdwn" ,
240
- "text" : self ._generate_entities_list_with_level_and_link (blueprint ,
241
- entities_didnt_pass_all_rules )
242
- }
243
- }
239
+ * self ._generate_entities_list_with_level_and_link (
240
+ blueprint ,
241
+ entities_didnt_pass_all_rules
242
+ )
244
243
]
245
244
return blocks
246
245
@@ -285,7 +284,11 @@ def _resolve_top_highest_lowest_scored_rules(entities: list,
285
284
if not matched :
286
285
filtered_top_lowest_scored_rules_by_percentage .append ((lowest_rule , value ))
287
286
288
- return top_3_highest_scored_rules_by_percentage , filtered_top_lowest_scored_rules_by_percentage
287
+ return (
288
+ top_3_highest_scored_rules_by_percentage ,
289
+ filtered_top_lowest_scored_rules_by_percentage
290
+ )
291
+
289
292
290
293
@staticmethod
291
294
def _calculate_top_teams_by_percentage (entities : list ,
@@ -303,16 +306,102 @@ def _calculate_top_teams_by_percentage(entities: list,
303
306
return top_3_teams_by_percentage
304
307
305
308
@staticmethod
306
- def _generate_entities_list_with_level_and_link (blueprint : str ,
307
- entities_by_level : Dict [str , List [Dict [str , str ]]]) -> str :
308
- text = ""
309
- base_entity_url = f"{ get_port_url (settings .port_region , 'app' )} /{ blueprint } Entity?identifier="
309
+ def _generate_entities_list_with_level_and_link (
310
+ blueprint : str ,
311
+ entities_by_level : Dict [str , List [Dict [str , str ]]]
312
+ ) -> list [dict [str , str ]]:
313
+ """
314
+ Generates message block for Slack with entities grouped by level,
315
+ being aware of the 3001 message limit for Slack blocks.
316
+ """
317
+ block = []
310
318
for level , entities in entities_by_level .items ():
311
319
if not entities :
312
320
continue
321
+
322
+ level_title = f"*{ level } *\n \n "
323
+
324
+ # we must account for the length of the level title
325
+ # in the max size of a slack message block
326
+ max_message_block_size_for_entities = (
327
+ SlackMessageGenerator .SLACK_MAX_MESSAGE_BLOCK_SIZE
328
+ - len (level_title )
329
+ )
330
+ # let's us know if this is the first batch of entities
331
+ # which will contain the title of the level
332
+ batch_count = 0
333
+ current_count = 0
334
+ # the entities that will be written to slack at this current
335
+ # iteration
336
+ current_entities = []
313
337
314
- text += f"\n *{ level } *\n \n "
315
- for entity in entities :
316
- text += f"• <{ base_entity_url } { entity .get ('identifier' )} |{ entity .get ('name' )} >" \
317
- f" - `[{ len (entity .get ('passed_rules' ))} /{ entity .get ('number_of_rules' )} ] Passed` \n "
318
- return text
338
+ while current_count < len (entities ):
339
+ # we will keep adding entities to the current_entities list
340
+ # until we reach the max size of a slack message block
341
+ text_length = 0
342
+ entities_text = ""
343
+ while text_length < (
344
+ max_message_block_size_for_entities
345
+ ) and current_count < len (entities ):
346
+ current_entities .append (entities [current_count ])
347
+ entities_text = " \n " .join (
348
+ SlackMessageGenerator ._generate_text_for_entity (blueprint , entity )
349
+ for entity in current_entities
350
+ )
351
+ text_length = len (entities_text )
352
+ current_count += 1
353
+
354
+ # if we reach the end of the entities list,
355
+ # we will generate the block
356
+ if current_count == len (entities ):
357
+ block .append (
358
+ SlackMessageGenerator ._generate_block_for_entities (
359
+ current_entities ,
360
+ blueprint ,
361
+ level_title if batch_count == 0 else ""
362
+ )
363
+ )
364
+ break
365
+
366
+ # the only way we can reach this point is if the current_entities
367
+ # list is above the max size of a slack message block
368
+ # if we simply make the request, the API call will fail
369
+ # therefore, we will remove the last entity from the list
370
+ # to stay within the limit.
371
+ current_entities .pop ()
372
+ # update the current count to reflect the removal of the last entity
373
+ current_count -= 1
374
+ block .append (
375
+ SlackMessageGenerator ._generate_block_for_entities (
376
+ current_entities ,
377
+ blueprint ,
378
+ level_title if batch_count == 0 else ""
379
+ )
380
+ )
381
+ current_entities = []
382
+ batch_count += 1
383
+ return block
384
+
385
+ @staticmethod
386
+ def _generate_block_for_entities (entities : List [Dict [str , str ]], blueprint : str , prefix : str = "" ) -> Dict [str , Any ]:
387
+ return {
388
+ "type" : "section" ,
389
+ "text" : {
390
+ "type" : "mrkdwn" ,
391
+ "text" : (
392
+ prefix +
393
+ " \n " .join (
394
+ SlackMessageGenerator ._generate_text_for_entity (blueprint , entity )
395
+ for entity in entities
396
+ )
397
+ )
398
+ }
399
+ }
400
+
401
+ @staticmethod
402
+ def _generate_text_for_entity (blueprint : str , entity : Dict [str , str ]) -> str :
403
+ base_entity_url = f"{ get_port_url (settings .port_region , 'app' )} /{ blueprint } Entity?identifier="
404
+ return (
405
+ f"• <{ base_entity_url } { entity .get ('identifier' )} |{ entity .get ('name' )} >"
406
+ f" - `[{ len (entity .get ('passed_rules' ))} /{ entity .get ('number_of_rules' )} ] Passed`"
407
+ )
0 commit comments