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
예제 #2
0
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
예제 #6
0
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
예제 #7
0
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
예제 #8
0
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