class LeaveOpenNoActivity(BzCleaner): def __init__(self): super(LeaveOpenNoActivity, self).__init__() self.people = People() self.nmonths = utils.get_config(self.name(), 'months_lookup') self.max_ni = utils.get_config(self.name(), 'max_ni') self.blacklist = set(utils.get_config(self.name(), 'blacklist', [])) def description(self): return 'Bugs with leave-open keyword and no activity for the last {} months'.format( self.nmonths ) def get_extra_for_needinfo_template(self): return self.get_extra_for_template() def get_extra_for_template(self): return {'nmonths': self.nmonths} def get_auto_ni_blacklist(self): return self.blacklist def get_max_ni(self): return self.max_ni def get_mail_to_auto_ni(self, bug): for f in ['assigned_to', 'triage_owner']: person = bug.get(f, '') if person and self.people.is_mozilla(person): return {'mail': person, 'nickname': bug[f + '_detail']['nick']} return None def get_bz_params(self, date): fields = ['assigned_to', 'triage_owner'] params = { 'include_fields': fields, 'resolution': '---', 'f1': 'keywords', 'o1': 'casesubstring', 'v1': 'leave-open', 'f2': 'keywords', 'o2': 'notsubstring', 'v2': 'intermittent', 'f3': 'status_whiteboard', 'o3': 'notregexp', 'v3': r'\[(test|stockwell) disabled.*\]', 'f4': 'days_elapsed', 'o4': 'greaterthan', 'v4': self.nmonths * 30, } return params
class LeaveOpenNoActivity(BzCleaner): def __init__(self): super(LeaveOpenNoActivity, self).__init__() self.people = People() self.nmonths = utils.get_config(self.name(), 'months_lookup') self.max_ni = utils.get_config(self.name(), 'max_ni') self.skiplist = set(utils.get_config(self.name(), 'skiplist', [])) def description(self): return 'Bugs with leave-open keyword and no activity for the last {} months'.format( self.nmonths) def get_extra_for_needinfo_template(self): return self.get_extra_for_template() def get_extra_for_template(self): return {'nmonths': self.nmonths} def get_auto_ni_skiplist(self): return self.skiplist def get_max_ni(self): return self.max_ni def get_mail_to_auto_ni(self, bug): for f in ['assigned_to', 'triage_owner']: person = bug.get(f, '') if person and self.people.is_mozilla(person): return {'mail': person, 'nickname': bug[f + '_detail']['nick']} return None def get_bz_params(self, date): fields = ['assigned_to', 'triage_owner'] params = { 'include_fields': fields, 'resolution': '---', 'f1': 'keywords', 'o1': 'casesubstring', 'v1': 'leave-open', 'f2': 'keywords', 'o2': 'notsubstring', 'v2': 'intermittent', 'f3': 'status_whiteboard', 'o3': 'notregexp', 'v3': r'\[(test|stockwell) disabled.*\]', 'f4': 'days_elapsed', 'o4': 'greaterthan', 'v4': self.nmonths * 30, } return params
class MetaNoDepsNoActivity(BzCleaner): def __init__(self): super(MetaNoDepsNoActivity, self).__init__() self.people = People() self.nmonths = utils.get_config(self.name(), 'months_lookup') self.max_ni = utils.get_config(self.name(), 'max_ni') self.blacklist = set(utils.get_config(self.name(), 'blacklist', [])) def description(self): return 'Bugs with meta keyword, not depending on bugs and no activity for the last {} months'.format( self.nmonths ) def get_extra_for_needinfo_template(self): return self.get_extra_for_template() def get_extra_for_template(self): return {'nmonths': self.nmonths} def get_auto_ni_blacklist(self): return self.blacklist def get_max_ni(self): return self.max_ni def get_mail_to_auto_ni(self, bug): for f in ['assigned_to', 'triage_owner']: person = bug.get(f, '') if person and self.people.is_mozilla(person): return {'mail': person, 'nickname': bug[f + '_detail']['nick']} return None def get_bz_params(self, date): fields = ['assigned_to', 'triage_owner'] params = { 'include_fields': fields, 'resolution': '---', 'f1': 'keywords', 'o1': 'casesubstring', 'v1': 'meta', 'f2': 'days_elapsed', 'o2': 'greaterthan', 'v2': self.nmonths * 30, 'f3': 'dependson', 'o3': 'isempty', } return params
class MetaNoDepsNoActivity(BzCleaner): def __init__(self): super(MetaNoDepsNoActivity, self).__init__() self.people = People() self.nmonths = utils.get_config(self.name(), 'months_lookup') self.max_ni = utils.get_config(self.name(), 'max_ni') self.skiplist = set(utils.get_config(self.name(), 'skiplist', [])) def description(self): return 'Bugs with meta keyword, not depending on bugs and no activity for the last {} months'.format( self.nmonths) def get_extra_for_needinfo_template(self): return self.get_extra_for_template() def get_extra_for_template(self): return {'nmonths': self.nmonths} def get_auto_ni_skiplist(self): return self.skiplist def get_max_ni(self): return self.max_ni def get_mail_to_auto_ni(self, bug): for f in ['assigned_to', 'triage_owner']: person = bug.get(f, '') if person and self.people.is_mozilla(person): return {'mail': person, 'nickname': bug[f + '_detail']['nick']} return None def get_bz_params(self, date): fields = ['assigned_to', 'triage_owner'] params = { 'include_fields': fields, 'resolution': '---', 'f1': 'keywords', 'o1': 'casesubstring', 'v1': 'meta', 'f2': 'days_elapsed', 'o2': 'greaterthan', 'v2': self.nmonths * 30, 'f3': 'dependson', 'o3': 'isempty', } return params
class NewbieWithNI(BzCleaner): def __init__(self): super(NewbieWithNI, self).__init__() self.people = People() self.ndays = utils.get_config(self.name(), 'number_of_days', 14) self.ncomments = utils.get_config(self.name(), 'number_of_comments', 2) self.autofix_reporters = {} def description(self): return 'Bugs where the reporter has a needinfo and no activity for the last {} weeks'.format( self.ndays ) def get_extra_for_template(self): return {'ndays': self.ndays, 'ncomments': self.ncomments} def get_autofix_change(self): return self.autofix_reporters def set_autofix(self, bugs): for bugid, v in bugs.items(): nick = v['creator_nick'] self.autofix_reporters[bugid] = { 'comment': { 'body': 'Closing as INCOMPLETE because no answer to the needinfo.\n:{}, feel free to reopen the bug if you\'ve more information to provide.'.format( nick ) }, 'status': 'RESOLVED', 'resolution': 'INCOMPLETE', } def filter_interesting_bugs(self, bugs): """Get the bugs with number of comments less than self.ncommments """ def comment_handler(bug, bugid, data): if len(bug['comments']) <= self.ncomments: data.append(bugid) bugids = list(bugs.keys()) data = [] Bugzilla( bugids=bugids, commenthandler=comment_handler, commentdata=data, comment_include_fields=['count'], ).get_data().wait() bugs = {bugid: bugs[bugid] for bugid in data} return bugs def get_bz_params(self, date): fields = ['creator', 'flags'] params = { 'include_fields': fields, 'resolution': '---', 'f1': 'flagtypes.name', 'o1': 'substring', 'v1': 'needinfo?', 'f2': 'days_elapsed', 'o2': 'greaterthan', 'v2': self.ndays, } return params def handle_bug(self, bug, data): creator = bug['creator'] if self.people.is_mozilla(creator): return None for flag in utils.get_needinfo(bug): if flag.get('requestee', '') == creator: bugid = str(bug['id']) nick = bug['creator_detail']['nick'] data[bugid] = {'creator_nick': nick} return bug return None def get_bugs(self, date='today', bug_ids=[]): bugs = super(NewbieWithNI, self).get_bugs(date=date, bug_ids=bug_ids) bugs = self.filter_interesting_bugs(bugs) self.set_autofix(bugs) return bugs
class Nag(object): def __init__(self): super(Nag, self).__init__() self.people = People() self.send_nag_mail = True self.data = {} self.nag_date = None self.white_list = [] self.black_list = [] self.escalation = Escalation(self.people) self.triage_owners = {} self.all_owners = None self.query_params = {} @staticmethod def get_from(): return utils.get_config('auto_nag', 'from', '*****@*****.**') def get_cc(self): cc = self.get_config('cc', None) if cc is None: cc = utils.get_config('auto_nag', 'cc', []) return set(cc) def get_priority(self, bug): tracking = bug[self.tracking] if tracking == 'blocking': return 'high' return 'normal' def filter_bug(self, priority): days = (utils.get_next_release_date() - self.nag_date).days weekday = self.nag_date.weekday() return self.escalation.filter(priority, days, weekday) def get_people(self): return self.people def set_people_to_nag(self, bug, buginfo): return bug def escalate(self, person, priority, **kwargs): days = (utils.get_next_release_date() - self.nag_date).days return self.escalation.get_supervisor(priority, days, person, **kwargs) def add(self, person, bug_data, priority='default', **kwargs): if not self.people.is_mozilla(person): return False manager = self.escalate(person, priority, **kwargs) return self.add_couple(person, manager, bug_data) def add_couple(self, person, manager, bug_data): person = self.people.get_moz_mail(person) if manager in self.data: data = self.data[manager] else: self.data[manager] = data = {} if person in data: data[person].append(bug_data) else: data[person] = [bug_data] return True def nag_template(self): return self.name() + '_nag.html' def get_extra_for_nag_template(self): return {} def columns_nag(self): return None def sort_columns_nag(self): return None def _is_in_list(self, mail, _list): for manager in _list: if self.people.is_under(mail, manager): return True return False def is_under(self, mail): if not self.white_list: if not self.black_list: return True return not self._is_in_list(mail, self.black_list) if not self.black_list: return self._is_in_list(mail, self.white_list) return self._is_in_list(mail, self.white_list) and not self._is_in_list( mail, self.black_list ) def add_triage_owner(self, owner, real_owner=None): if owner not in self.triage_owners: to = real_owner if real_owner is not None else owner self.triage_owners[owner] = self.get_query_url_for_triage_owner(to) def get_query_url_for_triage_owner(self, owner): if self.all_owners is None: self.all_owners = utils.get_triage_owners() params = copy.deepcopy(self.query_params) if 'include_fields' in params: del params['include_fields'] comps = self.all_owners[owner] comps = set(comps) params['component'] = sorted(comps) url = utils.get_bz_search_url(params) return url def organize_nag(self, bugs): columns = self.columns_nag() if columns is None: columns = self.columns() key = self.sort_columns_nag() if key is None: key = self.sort_columns() return utils.organize(bugs, columns, key=key) def send_mails(self, title, dryrun=False): if not self.send_nag_mail: return env = Environment(loader=FileSystemLoader('templates')) common = env.get_template('common.html') login_info = utils.get_login_info() From = Nag.get_from() Default_Cc = self.get_cc() mails = self.prepare_mails() for m in mails: Cc = Default_Cc.copy() if m['manager']: Cc.add(m['manager']) body = common.render(message=m['body'], query_url=None) receivers = set(m['to']) | set(Cc) status = 'Success' try: mail.send( From, sorted(m['to']), title, body, Cc=sorted(Cc), html=True, login=login_info, dryrun=dryrun, ) except: # NOQA logger.exception('Tool {}'.format(self.name())) status = 'Failure' db.Email.add(self.name(), receivers, 'individual', status) def prepare_mails(self): if not self.data: return [] template = self.nag_template() if not template: return [] extra = self.get_extra_for_nag_template() env = Environment(loader=FileSystemLoader('templates')) template = env.get_template(template) mails = [] for manager, info in self.data.items(): data = [] To = sorted(info.keys()) for person in To: bug_data = info[person] data += bug_data if len(To) == 1 and To[0] in self.triage_owners: query_url = self.triage_owners[To[0]] else: query_url = None body = template.render( date=self.nag_date, extra=extra, plural=utils.plural, enumerate=enumerate, data=self.organize_nag(data), nag=True, query_url_nag=query_url, table_attrs=self.get_config('table_attrs'), ) m = {'manager': manager, 'to': set(info.keys()), 'body': body} mails.append(m) return mails def reorganize_to_bag(self, data): return data
class RoundRobin(object): def __init__(self, rr=None, people=None): self.feed(rr=rr) self.nicks = {} self.people = People() if people is None else people def feed(self, rr=None): self.data = {} filenames = {} if rr is None: rr = {} for team, path in utils.get_config('round-robin', "teams", default={}).items(): with open('./auto_nag/scripts/configs/{}'.format(path), 'r') as In: rr[team] = json.load(In) filenames[team] = path # rr is dictionary: # - doc -> documentation # - triagers -> dictionary: Real Name -> {bzmail: bugzilla email, nick: bugzilla nickname} # - components -> dictionary: Product::Component -> strategy name # - strategies: dictionay: {duty en date -> Real Name} # Get all the strategies for each team for team, data in rr.items(): if 'doc' in data: del data['doc'] strategies = {} triagers = data['triagers'] fallback_bzmail = triagers['Fallback']['bzmail'] path = filenames.get(team, '') # collect strategies for pc, strategy in data['components'].items(): strategy_data = data[strategy] if strategy not in strategies: strategies[strategy] = strategy_data # rewrite strategy to have a sorted list of end dates for strat_name, strategy in strategies.items(): if 'doc' in strategy: del strategy['doc'] date_name = [] # end date and real name of the triager for date, name in strategy.items(): # collect the tuple (end_date, bzmail) date = lmdutils.get_date_ymd(date) bzmail = triagers[name]['bzmail'] date_name.append((date, bzmail)) # we sort the list to use bisection to find the triager date_name = sorted(date_name) strategies[strat_name] = { 'dates': [d for d, _ in date_name], 'mails': [m for _, m in date_name], 'fallback': fallback_bzmail, 'filename': path, } # finally self.data is a dictionary: # - Product::Component -> dictionary {dates: sorted list of end date # mails: list # fallback: who to nag when we've nobody # filename: the file containing strategy} for pc, strategy in data['components'].items(): self.data[pc] = strategies[strategy] def get_nick(self, bzmail): if bzmail not in self.nicks: def handler(user): self.nicks[bzmail] = user['nick'] BugzillaUser(user_names=[bzmail], user_handler=handler).wait() return self.nicks[bzmail] def is_mozilla(self, bzmail): return self.people.is_mozilla(bzmail) def get(self, bug, date): pc = '{}::{}'.format(bug['product'], bug['component']) if pc not in self.data: mail = bug['triage_owner'] nick = bug['triage_owner_detail']['nick'] return mail, nick date = lmdutils.get_date_ymd(date) strategy = self.data[pc] dates = strategy['dates'] i = bisect.bisect_left(strategy['dates'], date) if i == len(dates): bzmail = strategy['fallback'] else: bzmail = strategy['mails'][i] nick = self.get_nick(bzmail) return bzmail, nick def get_who_to_nag(self, date): fallbacks = {} date = lmdutils.get_date_ymd(date) days = utils.get_config('round-robin', 'days_to_nag', 7) for pc, strategy in self.data.items(): last_date = strategy['dates'][-1] if (last_date - date ).days <= days and strategy['filename'] not in fallbacks: fallbacks[strategy['filename']] = strategy['fallback'] # create a dict: mozmail -> list of filenames to check res = {} for fn, fb in fallbacks.items(): if not self.is_mozilla(fb): raise BadFallback() mozmail = self.people.get_moz_mail(fb) if mozmail not in res: res[mozmail] = [] res[mozmail].append(fn) res = {fb: sorted(fn) for fb, fn in res.items()} return res
class Nag(object): def __init__(self): super(Nag, self).__init__() self.people = People() self.send_nag_mail = True self.data = {} self.nag_date = None self.white_list = [] self.black_list = [] self.escalation = Escalation(self.people) self.triage_owners = {} self.all_owners = None self.query_params = {} @staticmethod def get_from(): return utils.get_config('auto_nag', 'from', '*****@*****.**') @staticmethod def get_cc(): return set(utils.get_config('auto_nag', 'cc', [])) def get_priority(self, bug): tracking = bug[self.tracking] if tracking == 'blocking': return 'high' return 'normal' def filter_bug(self, priority): days = (utils.get_next_release_date() - self.nag_date).days weekday = self.nag_date.weekday() return self.escalation.filter(priority, days, weekday) def get_people(self): return self.people def set_people_to_nag(self, bug, buginfo): return bug def escalate(self, person, priority, **kwargs): days = (utils.get_next_release_date() - self.nag_date).days return self.escalation.get_supervisor(priority, days, person, **kwargs) def add(self, person, bug_data, priority='default', **kwargs): if not self.people.is_mozilla(person): return False manager = self.escalate(person, priority, **kwargs) return self.add_couple(person, manager, bug_data) def add_couple(self, person, manager, bug_data): person = self.people.get_moz_mail(person) if manager in self.data: data = self.data[manager] else: self.data[manager] = data = {} if person in data: data[person].append(bug_data) else: data[person] = [bug_data] return True def nag_template(self): return self.name() + '_nag.html' def get_extra_for_nag_template(self): return {} def columns_nag(self): return None def sort_columns_nag(self): return None def _is_in_list(self, mail, _list): for manager in _list: if self.people.is_under(mail, manager): return True return False def is_under(self, mail): if not self.white_list: if not self.black_list: return True return not self._is_in_list(mail, self.black_list) if not self.black_list: return self._is_in_list(mail, self.white_list) return self._is_in_list(mail, self.white_list) and not self._is_in_list( mail, self.black_list ) def add_triage_owner(self, owner, real_owner=None): if owner not in self.triage_owners: to = real_owner if real_owner is not None else owner self.triage_owners[owner] = self.get_query_url_for_triage_owner(to) def get_query_url_for_triage_owner(self, owner): if self.all_owners is None: self.all_owners = utils.get_triage_owners() params = copy.deepcopy(self.query_params) if 'include_fields' in params: del params['include_fields'] comps = self.all_owners[owner] comps = set(comps) params['component'] = sorted(comps) url = utils.get_bz_search_url(params) return url def organize_nag(self, bugs): columns = self.columns_nag() if columns is None: columns = self.columns() key = self.sort_columns_nag() if key is None: key = self.sort_columns() return utils.organize(bugs, columns, key=key) def send_mails(self, title, dryrun=False): if not self.send_nag_mail: return env = Environment(loader=FileSystemLoader('templates')) common = env.get_template('common.html') login_info = utils.get_login_info() From = Nag.get_from() Default_Cc = Nag.get_cc() mails = self.prepare_mails() for m in mails: Cc = Default_Cc.copy() if m['manager']: Cc.add(m['manager']) body = common.render(message=m['body'], query_url=None) receivers = set(m['to']) | set(Cc) status = 'Success' try: mail.send( From, sorted(m['to']), title, body, Cc=sorted(Cc), html=True, login=login_info, dryrun=dryrun, ) except: # NOQA logger.exception('Tool {}'.format(self.name())) status = 'Failure' db.Email.add(self.name(), receivers, 'individual', status) def prepare_mails(self): if not self.data: return [] template = self.nag_template() if not template: return [] extra = self.get_extra_for_nag_template() env = Environment(loader=FileSystemLoader('templates')) template = env.get_template(template) mails = [] for manager, info in self.data.items(): data = [] To = sorted(info.keys()) for person in To: bug_data = info[person] data += bug_data if len(To) == 1 and To[0] in self.triage_owners: query_url = self.triage_owners[To[0]] else: query_url = None body = template.render( date=self.nag_date, extra=extra, plural=utils.plural, enumerate=enumerate, data=self.organize_nag(data), nag=True, query_url_nag=query_url, table_attrs=self.get_config('table_attrs'), ) m = {'manager': manager, 'to': set(info.keys()), 'body': body} mails.append(m) return mails def reorganize_to_bag(self, data): return data