11# Python Standard Library Imports
22import datetime
33import random
4+ from dataclasses import dataclass
45
56# Third Party (PyPI) Imports
67import emoji
2425# isort: off
2526
2627
28+ @dataclass
29+ class ReportSectionConfig :
30+ revisions : list
31+ emoji : str
32+ suffix : str
33+ order_asc : bool = True
34+ slack_color : str = None
35+ include_in_slack : bool = True
36+
37+ @property
38+ def num_revisions (self ):
39+ return len (self .revisions )
40+
41+ @property
42+ def order_str (self ):
43+ value = ('oldest' if self .order_asc else 'newest' ) + ' first'
44+ return value
45+
46+
2747class RevisionStatusReport (PhablyticsReport ):
2848 """The Revision Status Report shows a list of Diffs being worked on by a team,
2949 and outputs them based on their acceptance/needs review status
@@ -65,6 +85,7 @@ def _prepare_report(self):
6585 )
6686
6787 # place revisions into buckets
88+ revisions_wip = []
6889 revisions_to_review = []
6990 revisions_with_blocks = []
7091 revisions_additional_approval = []
@@ -74,8 +95,7 @@ def _prepare_report(self):
7495 if revision .meets_acceptance_criteria :
7596 revisions_accepted .append (revision )
7697 elif revision .is_wip :
77- # skip WIP
78- pass
98+ revisions_wip .append (revision )
7999 elif revision .num_blockers > 0 :
80100 revisions_with_blocks .append (revision )
81101 elif 0 < revision .num_acceptors < REVISION_ACCEPTANCE_THRESHOLD :
@@ -109,6 +129,7 @@ def _prepare_report(self):
109129 revisions_blocked .append (revision )
110130
111131 # finally, assign bucketed revisions
132+ self .revisions_wip = revisions_wip
112133 self .revisions_to_review = revisions_to_review
113134 self .revisions_change_required = revisions_change_required
114135 self .revisions_blocked = revisions_blocked
@@ -181,91 +202,91 @@ def _format_and_append_revision_to_report(self, report, revision, count, slack=T
181202
182203 report .append ('' )
183204
205+ def get_report_section_configs (self ):
206+ configs = [
207+ # Revisions WIP
208+ ReportSectionConfig (
209+ revisions = self .revisions_wip ,
210+ emoji = ':construction:' ,
211+ suffix = f"{ pluralize_verb ('is' , len (self .revisions_wip ))} currently WIP" ,
212+ order_asc = False ,
213+ slack_color = '#ccccc' , # light gray
214+ include_in_slack = False
215+ ),
216+ # Revisions with 0 reviews
217+ ReportSectionConfig (
218+ revisions = self .revisions_to_review ,
219+ emoji = ':warning:' ,
220+ suffix = f"{ pluralize_verb ('need' , len (self .revisions_to_review ))} to be reviewed" ,
221+ order_asc = False ,
222+ # slack_color='warning' # Slack-yellow, has an orange hue
223+ slack_color = '#f2c744' # yellow
224+ ),
225+ # Revisions with team blockers - change required
226+ ReportSectionConfig (
227+ revisions = self .revisions_change_required ,
228+ emoji = ':arrows_counterclockwise:' ,
229+ suffix = f"{ pluralize_verb ('require' , len (self .revisions_change_required ))} changes" ,
230+ order_asc = False ,
231+ slack_color = '#e8912d' # orange
232+ ),
233+ # Revisions with only external blockers
234+ ReportSectionConfig (
235+ revisions = self .revisions_blocked ,
236+ emoji = ':no_entry_sign:' ,
237+ suffix = f"{ pluralize_verb ('is' , len (self .revisions_blocked ))} blocked" ,
238+ order_asc = False ,
239+ slack_color = 'danger' # Slack red
240+ ),
241+ # Revisions with 1 approval
242+ ReportSectionConfig (
243+ revisions = self .revisions_additional_approval ,
244+ emoji = ':pray:' ,
245+ suffix = f"{ pluralize_verb ('need' , len (self .revisions_additional_approval ))} additional approvals" ,
246+ order_asc = False ,
247+ slack_color = '#439f30' # blue
248+ ),
249+ # Revisions Accepted
250+ ReportSectionConfig (
251+ revisions = self .revisions_accepted ,
252+ emoji = ':white_check_mark:' ,
253+ suffix = f"{ pluralize_verb ('is' , len (self .revisions_accepted ))} accepted and ready to land" ,
254+ order_asc = True ,
255+ slack_color = 'good' ,
256+ )
257+ ]
258+
259+ return configs
260+
184261 def generate_slack_report (self ):
185262 """Generates the Revision Status Report with Slack attachments
186263 """
187264 attachments = []
188265
189- # Revisions with 0 reviews
190- num_revisions = len (self .revisions_to_review )
191- if num_revisions > 0 :
192- report = []
193- count = 0
194-
195- for revision in sorted (self .revisions_to_review , key = lambda r : r .modified_ts , reverse = True ):
196- count += 1
197- self ._format_and_append_revision_to_report (report , revision , count )
198-
199- attachments .append ({
200- 'pretext' : f":warning: *{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('need' , num_revisions )} to be reviewed*: _(newest first)_" ,
201- 'text' : '\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ),
202- # 'color': 'warning', # Slack-yellow, has an orange hue
203- 'color' : '#f2c744' ,
204- })
205-
206- # Revisions with team blockers - change required
207- num_revisions = len (self .revisions_change_required )
208- if num_revisions > 0 :
209- report = []
210- count = 0
211-
212- for revision in sorted (self .revisions_change_required , key = lambda r : r .modified_ts , reverse = True ):
213- count += 1
214- self ._format_and_append_revision_to_report (report , revision , count )
215-
216- attachments .append ({
217- 'pretext' : f":arrows_counterclockwise: *{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('require' , num_revisions )} changes*: _(newest first)_" ,
218- 'text' : '\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ),
219- 'color' : '#e8912d' , # orange
220- })
221-
222- # Revisions with only external blockers
223- num_revisions = len (self .revisions_blocked )
224- if num_revisions > 0 :
225- report = []
226- count = 0
227-
228- for revision in sorted (self .revisions_blocked , key = lambda r : r .modified_ts , reverse = True ):
229- count += 1
230- self ._format_and_append_revision_to_report (report , revision , count )
231-
232- attachments .append ({
233- 'pretext' : f":no_entry_sign: *{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('is' , num_revisions )} blocked*: _(newest first)_" ,
234- 'text' : '\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ),
235- 'color' : 'danger' ,
236- })
237-
238- # Revisions with 1 approval
239- num_revisions = len (self .revisions_additional_approval )
240- if num_revisions > 0 :
241- report = []
242- count = 0
243-
244- for revision in sorted (self .revisions_additional_approval , key = lambda r : r .modified_ts , reverse = True ):
245- count += 1
246- self ._format_and_append_revision_to_report (report , revision , count )
247-
248- attachments .append ({
249- 'pretext' : f":pray: *{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('need' , num_revisions )} additional approvals*: _(newest first)_" ,
250- 'text' : '\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ),
251- 'color' : '#439fe0' , # blue
252- })
253-
254- # Revisions Accepted
255- num_revisions = len (self .revisions_accepted )
256- if num_revisions > 0 :
257- report = []
258- count = 0
259-
260- for revision in sorted (self .revisions_accepted , key = lambda r : r .modified_ts ):
261- count += 1
262- self ._format_and_append_revision_to_report (report , revision , count )
263-
264- attachments .append ({
265- 'pretext' : f":white_check_mark: *{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('is' , num_revisions )} accepted and ready to land*: _(oldest first)_" ,
266- 'text' : '\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ),
267- 'color' : 'good' ,
268- })
266+ report_section_configs = self .get_report_section_configs ()
267+
268+ def _build_report_section (report_section_config ):
269+ num_revisions = report_section_config .num_revisions
270+ if num_revisions > 0 :
271+ report = []
272+ count = 0
273+
274+ for revision in sorted (report_section_config .revisions , key = lambda r : r .modified_ts , reverse = not report_section_config .order_asc ):
275+ count += 1
276+ self ._format_and_append_revision_to_report (report , revision , count )
277+
278+ attachments .append ({
279+ 'pretext' : f"{ report_section_config .emoji } *{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { report_section_config .suffix } *: _({ report_section_config .order_str } )_" ,
280+ 'text' : '\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ),
281+ 'color' : report_section_config .slack_color ,
282+ })
283+
284+ for report_section_config in report_section_configs :
285+ if report_section_config .include_in_slack :
286+ _build_report_section (report_section_config )
287+ else :
288+ # exclude sections that might be too noisy for Slack, but include in web
289+ pass
269290
270291 DIFF_PRESENT_MESSAGES = [
271292 "Let's review some diffs!" ,
@@ -300,80 +321,26 @@ def generate_slack_report(self):
300321 def generate_text_report (self ):
301322 lines = []
302323
303- # Revisions with 0 reviews
304- num_revisions = len (self .revisions_to_review )
305- if num_revisions > 0 :
306- report = []
307- count = 0
308-
309- for revision in sorted (self .revisions_to_review , key = lambda r : r .modified_ts , reverse = True ):
310- count += 1
311- self ._format_and_append_revision_to_report (report , revision , count , slack = False )
312-
313- icon = emoji .emojize (':warning:' )
314- lines .append (f"{ icon } { HTML_ICON_SEPARATOR } **{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('need' , num_revisions )} to be reviewed**: *(newest first)*" )
315- lines .append ('' )
316- lines .append ('\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ))
317-
318- # Revisions with team blockers - change required
319- num_revisions = len (self .revisions_change_required )
320- if num_revisions > 0 :
321- report = []
322- count = 0
323-
324- for revision in sorted (self .revisions_change_required , key = lambda r : r .modified_ts , reverse = True ):
325- count += 1
326- self ._format_and_append_revision_to_report (report , revision , count , slack = False )
327-
328- icon = emoji .emojize (':arrows_counterclockwise:' , use_aliases = True )
329- lines .append (f"{ icon } { HTML_ICON_SEPARATOR } **{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('require' , num_revisions )} changes**: *(newest first)*" )
330- lines .append ('' )
331- lines .append ('\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ))
332-
333- # Revisions with only external blockers
334- num_revisions = len (self .revisions_blocked )
335- if num_revisions > 0 :
336- report = []
337- count = 0
338-
339- for revision in sorted (self .revisions_blocked , key = lambda r : r .modified_ts , reverse = True ):
340- count += 1
341- self ._format_and_append_revision_to_report (report , revision , count , slack = False )
342-
343- icon = emoji .emojize (':no_entry_sign:' , use_aliases = True )
344- lines .append (f"{ icon } { HTML_ICON_SEPARATOR } **{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('is' , num_revisions )} blocked**: *(newest first)*" )
345- lines .append ('' )
346- lines .append ('\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ))
347-
348- # Revisions with 1 approval
349- num_revisions = len (self .revisions_additional_approval )
350- if num_revisions > 0 :
351- report = []
352- count = 0
353-
354- for revision in sorted (self .revisions_additional_approval , key = lambda r : r .modified_ts , reverse = True ):
355- count += 1
356- self ._format_and_append_revision_to_report (report , revision , count , slack = False )
357-
358- icon = emoji .emojize (':pray:' , use_aliases = True )
359- lines .append (f"{ icon } { HTML_ICON_SEPARATOR } **{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('need' , num_revisions )} additional approvals**: *(newest first)*" )
360- lines .append ('' )
361- lines .append ('\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ))
362-
363- # Revisions Accepted
364- num_revisions = len (self .revisions_accepted )
365- if num_revisions > 0 :
366- report = []
367- count = 0
368-
369- for revision in sorted (self .revisions_accepted , key = lambda r : r .modified_ts ):
370- count += 1
371- self ._format_and_append_revision_to_report (report , revision , count , slack = False )
372-
373- icon = emoji .emojize (':white_check_mark:' , use_aliases = True )
374- lines .append (f"{ icon } { HTML_ICON_SEPARATOR } **{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { pluralize_verb ('is' , num_revisions )} accepted and ready to land**: *(oldest first)*" )
375- lines .append ('' )
376- lines .append ('\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ))
324+ report_section_configs = self .get_report_section_configs ()
325+
326+ def _build_report_section (report_section_config ):
327+ num_revisions = report_section_config .num_revisions
328+ if num_revisions > 0 :
329+ report = []
330+ count = 0
331+
332+ for revision in sorted (report_section_config .revisions , key = lambda r : r .modified_ts , reverse = not report_section_config .order_asc ):
333+ count += 1
334+ self ._format_and_append_revision_to_report (report , revision , count , slack = False )
335+
336+ icon = emoji .emojize (report_section_config .emoji , use_aliases = True )
337+
338+ lines .append (f"{ icon } { HTML_ICON_SEPARATOR } **{ num_revisions } { pluralize_noun ('Diff' , num_revisions )} { report_section_config .suffix } **: *({ report_section_config .order_str } )*" )
339+ lines .append ('' )
340+ lines .append ('\n ' .join (report ).encode ('utf-8' ).decode ('utf-8' ))
341+
342+ for report_section_config in report_section_configs :
343+ _build_report_section (report_section_config )
377344
378345 text_report = '\n ' .join (lines )
379346 return text_report
0 commit comments