def get(self, bug, date):
        pc = bug['product'] + '::' + bug['component']
        if pc not in self.data:
            mail = bug.get('triage_owner')
            nick = bug.get('triage_owner_detail', {}).get('nick')
            if utils.is_no_assignee(mail):
                mail, nick = None, None

            if mail is None:
                logger.error('No triage owner for {}'.format(pc))

            return mail, nick

        cal = self.data[pc]
        persons = cal.get_persons(date)
        fb = cal.get_fallback_bzmail()
        if not persons or all(p is None for _, p in persons):
            return fb, self.get_nick(fb)

        bzmails = []
        for _, p in persons:
            bzmails.append(fb if p is None else p)

        bzmail = bzmails[randint(0, len(bzmails) - 1)]
        nick = self.get_nick(bzmail)

        return bzmail, nick
Exemple #2
0
 def add_erroneous_bzmail(self, bzmail, prod_comp, cal):
     logger.error(f"No nick for {bzmail} for {prod_comp}")
     fb = cal.get_fallback_mozmail()
     if fb not in self.erroneous_bzmail:
         self.erroneous_bzmail[fb] = {bzmail}
     else:
         self.erroneous_bzmail[fb].add(bzmail)
    def get(self, bug, date):
        pc = bug['product'] + '::' + bug['component']
        if pc not in self.data:
            mail = bug.get('triage_owner')
            nick = bug.get('triage_owner_detail', {}).get('nick')
            if utils.is_no_assignee(mail):
                mail, nick = None, None

            if mail is None:
                logger.error('No triage owner for {}'.format(pc))

            return mail, nick

        cal = self.data[pc]
        persons = cal.get_persons(date)
        fb = cal.get_fallback_bzmail()
        if not persons or all(p is None for _, p in persons):
            return fb, self.get_nick(fb)

        bzmails = []
        for _, p in persons:
            bzmails.append(fb if p is None else p)

        bzmail = bzmails[randint(0, len(bzmails) - 1)]
        nick = self.get_nick(bzmail)

        return bzmail, nick
Exemple #4
0
    def autofix(self, bugs):
        """Autofix the bugs according to what is returned by get_autofix_change"""
        ni_changes = self.set_needinfo()
        change = self.get_autofix_change()

        if not ni_changes and not change:
            return bugs

        self.has_autofix = True
        new_changes = {}
        if not self.has_individual_autofix(change):
            bugids = self.get_list_bugs(bugs)
            for bugid in bugids:
                new_changes[bugid] = utils.merge_bz_changes(
                    change, ni_changes.get(bugid, {})
                )
        else:
            change = {str(k): v for k, v in change.items()}
            bugids = set(change.keys()) | set(ni_changes.keys())
            for bugid in bugids:
                mrg = utils.merge_bz_changes(
                    change.get(bugid, {}), ni_changes.get(bugid, {})
                )
                if mrg:
                    new_changes[bugid] = mrg

        if self.dryrun or self.test_mode:
            for bugid, ch in new_changes.items():
                logger.info(
                    "The bugs: {}\n will be autofixed with:\n{}".format(bugid, ch)
                )
        else:
            extra = self.get_db_extra()
            max_retries = utils.get_config("common", "bugzilla_max_retries", 3)
            for bugid, ch in new_changes.items():
                added = False
                for _ in range(max_retries):
                    failures = Bugzilla([str(bugid)]).put(ch)
                    if failures:
                        time.sleep(1)
                    else:
                        added = True
                        db.BugChange.add(self.name(), bugid, extra=extra.get(bugid, ""))
                        break
                if not added:
                    self.failure_callback(bugid)
                    logger.error(
                        "{}: Cannot put data for bug {} (change => {}).".format(
                            self.name(), bugid, ch
                        )
                    )

        return bugs
Exemple #5
0
 def __init__(self):
     super(ToTriage, self).__init__()
     self.escalation = Escalation(self.people,
                                  data=self.get_config("escalation"))
     try:
         self.round_robin = RoundRobin.get_instance(
             teams=self.get_config("teams", []))
     except (BadFallback, InvalidCalendar) as err:
         logger.error(err)
     self.components = self.round_robin.get_components()
     for person in self.get_config("persons", []):
         self.components += utils.get_triage_owners()[person]
Exemple #6
0
    def get(self, bug, date, only_one=True, has_nick=True):
        pc = bug["product"] + "::" + bug["component"]
        if pc not in self.data:
            mail = bug.get("triage_owner")
            nick = bug.get("triage_owner_detail", {}).get("nick")
            if utils.is_no_assignee(mail):
                mail, nick = None, None

            if mail is None:
                logger.error("No triage owner for {}".format(pc))

            self.add_component_for_triager(pc, mail)

            if has_nick:
                return mail, nick if only_one else [(mail, nick)]
            return mail if only_one else [mail]

        cal = self.data[pc]
        persons = cal.get_persons(date)
        fb = cal.get_fallback_bzmail()
        if not persons or all(p is None for _, p in persons):
            # the fallback is the triage owner
            self.add_component_for_triager(pc, [fb])
            return (fb, self.get_nick(fb, pc, cal)) if has_nick else fb

        bzmails = []
        for _, p in persons:
            bzmails.append(fb if p is None else p)

        self.add_component_for_triager(pc, bzmails)

        if only_one:
            bzmail = bzmails[randint(0, len(bzmails) - 1)]
            if has_nick:
                nick = self.get_nick(bzmail, pc, cal)
                return bzmail, nick
            return bzmail

        if has_nick:
            return [(bzmail, self.get_nick(bzmail, pc, cal)) for bzmail in bzmails]
        return bzmails
    def feed(self, teams, rr=None):
        self.data = {}
        filenames = {}
        if rr is None:
            rr = {}
            for team, path in utils.get_config(
                "round-robin", "teams", default={}
            ).items():
                if teams is not None and team not in teams:
                    continue
                with open("./auto_nag/scripts/configs/{}".format(path), "r") as In:
                    rr[team] = json.load(In)
                    filenames[team] = path

        # rr is dictionary:
        # - doc -> documentation
        # - components -> dictionary: Product::Component -> strategy name
        # - strategies: dictionary: {calendar: url}

        to_remove = []

        # Get all the strategies for each team
        for team, data in rr.items():
            try:
                calendars = self.get_calendar(team, data)
                self.all_calendars += list(calendars.values())

                # finally self.data is a dictionary:
                # - Product::Component -> dictionary {fallback: who to nag when we've nobody
                #                                     calendar}
                for pc, strategy in data["components"].items():
                    self.data[pc] = calendars[strategy]
            except (BadFallback, InvalidCalendar) as err:
                logger.error(err)
                to_remove.append(team)

        for team in to_remove:
            del rr[team]
Exemple #8
0
    def get_bugs(self, date="today", bug_ids=[]):
        # Retrieve the bugs with the fields defined in get_bz_params
        raw_bugs = super().get_bugs(date=date,
                                    bug_ids=bug_ids,
                                    chunk_size=7000)

        if len(raw_bugs) == 0:
            return {}

        # Extract the bug ids
        bug_ids = list(raw_bugs.keys())

        # Classify those bugs
        bugs = get_bug_ids_classification("component", bug_ids)

        results = {}

        for bug_id in sorted(bugs.keys()):
            bug_data = bugs[bug_id]

            if not bug_data.get("available", True):
                # The bug was not available, it was either removed or is a
                # security bug
                continue

            if not {"prob", "index", "class", "extra_data"}.issubset(
                    bug_data.keys()):
                raise Exception(f"Invalid bug response {bug_id}: {bug_data!r}")

            bug = raw_bugs[bug_id]
            prob = bug_data["prob"]
            index = bug_data["index"]
            suggestion = bug_data["class"]
            conflated_components_mapping = bug_data["extra_data"][
                "conflated_components_mapping"]

            # Skip product-only suggestions that are not useful.
            if "::" not in suggestion and bug["product"] == suggestion:
                continue

            suggestion = conflated_components_mapping.get(
                suggestion, suggestion)

            if "::" not in suggestion:
                logger.error(
                    f"There is something wrong with this component suggestion! {suggestion}"
                )
                continue

            i = suggestion.index("::")
            suggested_product = suggestion[:i]
            suggested_component = suggestion[i + 2:]

            # When moving bugs out of the 'General' component, we don't want to change the product (unless it is Firefox).
            if bug["component"] == "General" and bug["product"] not in {
                    suggested_product,
                    "Firefox",
            }:
                continue

            # Don't move bugs from Firefox::General to Core::Internationalization.
            if (bug["product"] == "Firefox" and bug["component"] == "General"
                    and suggested_product == "Core"
                    and suggested_component == "Internationalization"):
                continue

            bug_id = str(bug["id"])

            result = {
                "id": bug_id,
                "summary": bug["summary"],
                "component": suggestion,
                "confidence": nice_round(prob[index]),
                "autofixed": False,
            }

            # In daily mode, we send an email with all results.
            if self.frequency == "daily":
                results[bug_id] = result

            confidence_threshold_conf = ("confidence_threshold"
                                         if bug["component"] != "General" else
                                         "general_confidence_threshold")

            if prob[index] >= self.get_config(confidence_threshold_conf):
                self.autofix_component[bug_id] = {
                    "product": suggested_product,
                    "component": suggested_component,
                }

                result["autofixed"] = True

                # In hourly mode, we send an email with only the bugs we acted upon.
                if self.frequency == "hourly":
                    results[bug_id] = result

        return results
    def get_bugs(self, date='today', bug_ids=[]):
        # Retrieve bugs to analyze.
        bugs, probs = super().get_bugs(date=date, bug_ids=bug_ids)
        if len(bugs) == 0:
            return {}

        # Get the encoded component.
        indexes = probs.argmax(axis=-1)
        # Apply inverse transformation to get the component name from the encoded value.
        suggestions = self.model.clf._le.inverse_transform(indexes)

        results = {}
        for bug, prob, index, suggestion in zip(bugs, probs, indexes,
                                                suggestions):
            # Skip product-only suggestions that are not useful.
            if '::' not in suggestion and bug['product'] == suggestion:
                continue

            suggestion = self.model.CONFLATED_COMPONENTS_MAPPING.get(
                suggestion, suggestion)

            if '::' not in suggestion:
                logger.error(
                    f'There is something wrong with this component suggestion! {suggestion}'
                )  # noqa
                continue

            i = suggestion.index('::')
            suggested_product = suggestion[:i]
            suggested_component = suggestion[i + 2:]  # NOQA

            # When moving bugs out of the 'General' component, we don't want to change the product (unless it is Firefox).
            if bug['component'] == 'General' and bug['product'] not in {
                    suggested_product,
                    'Firefox',
            }:
                continue

            bug_id = str(bug['id'])

            result = {
                'id': bug_id,
                'summary': self.get_summary(bug),
                'component': suggestion,
                'confidence': int(round(100 * prob[index])),
                'autofixed': False,
            }

            # In daily mode, we send an email with all results.
            if self.frequency == 'daily':
                results[bug_id] = result

            confidence_threshold_conf = ('confidence_threshold'
                                         if bug['component'] != 'General' else
                                         'general_confidence_threshold')

            if prob[index] >= self.get_config(confidence_threshold_conf):
                self.autofix_component[bug_id] = {
                    'product': suggested_product,
                    'component': suggested_component,
                }

                result['autofixed'] = True

                # In hourly mode, we send an email with only the bugs we acted upon.
                if self.frequency == 'hourly':
                    results[bug_id] = result

        return results
    def get_bugs(self, date='today', bug_ids=[]):
        # Retrieve bugs to analyze.
        bugs, probs = super().get_bugs(date=date, bug_ids=bug_ids)
        if len(bugs) == 0:
            return {}

        # Get the encoded component.
        indexes = probs.argmax(axis=-1)
        # Apply inverse transformation to get the component name from the encoded value.
        suggestions = self.model.clf._le.inverse_transform(indexes)

        results = {}
        for bug, prob, index, suggestion in zip(bugs, probs, indexes, suggestions):
            # Skip product-only suggestions that are not useful.
            if '::' not in suggestion and bug['product'] == suggestion:
                continue

            suggestion = self.model.CONFLATED_COMPONENTS_MAPPING.get(
                suggestion, suggestion
            )

            if '::' not in suggestion:
                logger.error(
                    f'There is something wrong with this component suggestion! {suggestion}'
                )  # noqa
                continue

            i = suggestion.index('::')
            suggested_product = suggestion[:i]
            suggested_component = suggestion[i + 2 :]  # NOQA

            # When moving bugs out of the 'General' component, we don't want to change the product (unless it is Firefox).
            if bug['component'] == 'General' and bug['product'] not in {
                suggested_product,
                'Firefox',
            }:
                continue

            bug_id = str(bug['id'])

            result = {
                'id': bug_id,
                'summary': self.get_summary(bug),
                'component': suggestion,
                'confidence': int(round(100 * prob[index])),
                'autofixed': False,
            }

            # In daily mode, we send an email with all results.
            if self.frequency == 'daily':
                results[bug_id] = result

            confidence_threshold_conf = (
                'confidence_threshold'
                if bug['component'] != 'General'
                else 'general_confidence_threshold'
            )

            if prob[index] >= self.get_config(confidence_threshold_conf):
                self.autofix_component[bug_id] = {
                    'product': suggested_product,
                    'component': suggested_component,
                }

                result['autofixed'] = True

                # In hourly mode, we send an email with only the bugs we acted upon.
                if self.frequency == 'hourly':
                    results[bug_id] = result

        return results