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
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 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
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]
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]
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