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
Beispiel #2
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
class Calendar(object):
    def __init__(self, cal, fallback, team_name, people=None):
        super(Calendar, self).__init__()
        self.cal = cal
        self.people = People() if people is None else people
        self.fallback = fallback
        self.fb_bzmail = self.people.get_bzmail_from_name(self.fallback)
        self.fb_mozmail = self.people.get_moz_mail(self.fb_bzmail)
        self.team_name = team_name
        self.cache = {}

    def get_fallback(self):
        return self.fallback

    def get_fallback_bzmail(self):
        if not self.fb_bzmail:
            raise BadFallback('\'{}\' is an invalid fallback'.format(self.fallback))
        return self.fb_bzmail

    def get_fallback_mozmail(self):
        if not self.fb_mozmail:
            raise BadFallback('\'{}\' is an invalid fallback'.format(self.fallback))
        return self.fb_mozmail

    def get_team_name(self):
        return self.team_name

    def get_persons(self, date):
        return []

    def set_team(self, team, triagers):
        self.team = []
        for p in team:
            if p in triagers and 'bzmail' in triagers[p]:
                bzmail = triagers[p]['bzmail']
            else:
                bzmail = self.people.get_bzmail_from_name(p)
            self.team.append((p, bzmail))

    @staticmethod
    def get(url, fallback, team_name, people=None):
        data = None
        if url.startswith('private://'):
            name = url.split('//', 1)[1]
            url = utils.get_private()[name]

        if url.startswith('http'):
            r = requests.get(url)
            data = r.text
        elif os.path.isfile(url):
            with open(url, 'r') as In:
                data = In.read()
        else:
            data = url

        if data is None:
            raise InvalidCalendar('Cannot read calendar: {}'.format(url))

        try:
            cal = json.loads(data)
            return JSONCalendar(cal, fallback, team_name, people=people)
        except JSONDecodeError:
            try:
                # there is an issue with dateutil.rrule parser when until doesn't have a tz
                # so a workaround is to add a Z at the end of the string.
                pat = re.compile(r'^RRULE:(.*)UNTIL=([0-9Z]+)', re.MULTILINE | re.I)

                def sub(m):
                    date = m.group(1)
                    if date.lower().endswith('z'):
                        return date
                    return date + 'Z'

                data = pat.sub(sub, data)

                return ICSCalendar(data, fallback, team_name, people=people)
            except ValueError:
                raise InvalidCalendar('Cannot decode calendar: {}'.format(url))
Beispiel #4
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
Beispiel #5
0
class Calendar(object):
    def __init__(self, cal, fallback, team_name, people=None):
        super(Calendar, self).__init__()
        self.cal = cal
        self.people = People() if people is None else people
        self.fallback = fallback
        self.fb_bzmail = self.people.get_bzmail_from_name(self.fallback)
        self.fb_mozmail = self.people.get_moz_mail(self.fb_bzmail)
        self.team_name = team_name
        self.cache = {}

    def get_fallback(self):
        return self.fallback

    def get_fallback_bzmail(self):
        if not self.fb_bzmail:
            raise BadFallback('\'{}\' is an invalid fallback'.format(
                self.fallback))
        return self.fb_bzmail

    def get_fallback_mozmail(self):
        if not self.fb_mozmail:
            raise BadFallback('\'{}\' is an invalid fallback'.format(
                self.fallback))
        return self.fb_mozmail

    def get_team_name(self):
        return self.team_name

    def get_persons(self, date):
        return []

    def set_team(self, team, triagers):
        self.team = []
        for p in team:
            if p in triagers and 'bzmail' in triagers[p]:
                bzmail = triagers[p]['bzmail']
            else:
                bzmail = self.people.get_bzmail_from_name(p)
            self.team.append((p, bzmail))

    @staticmethod
    def get(url, fallback, team_name, people=None):
        data = None
        if url.startswith('private://'):
            name = url.split('//', 1)[1]
            url = utils.get_private()[name]

        if url.startswith('http'):
            r = requests.get(url)
            data = r.text
        elif os.path.isfile(url):
            with open(url, 'r') as In:
                data = In.read()
        else:
            data = url

        if data is None:
            raise InvalidCalendar('Cannot read calendar: {}'.format(url))

        try:
            cal = json.loads(data)
            return JSONCalendar(cal, fallback, team_name, people=people)
        except JSONDecodeError:
            try:
                # there is an issue with dateutil.rrule parser when until doesn't have a tz
                # so a workaround is to add a Z at the end of the string.
                pat = re.compile(r'^RRULE:(.*)UNTIL=([0-9Z]+)',
                                 re.MULTILINE | re.I)

                def sub(m):
                    date = m.group(1)
                    if date.lower().endswith('z'):
                        return date
                    return date + 'Z'

                data = pat.sub(sub, data)

                return ICSCalendar(data, fallback, team_name, people=people)
            except ValueError:
                raise InvalidCalendar('Cannot decode calendar: {}'.format(url))