def Run(self, cnxn, services, allow_edit=True): """Updates an issue based on the parsed commands.""" try: issue = services.issue.GetIssueByLocalID( cnxn, self.project.project_id, self.local_id) except exceptions.NoSuchIssueException: return # Issue does not exist, so do nothing old_owner_id = issue.owner_id new_summary = self.parser.summary or issue.summary if self.parser.status is None: new_status = issue.status else: new_status = self.parser.status if self.parser.owner_id is None: new_owner_id = issue.owner_id else: new_owner_id = self.parser.owner_id new_cc_ids = [cc for cc in list(issue.cc_ids) + list(self.parser.cc_add) if cc not in self.parser.cc_remove] (new_labels, _update_add, _update_remove) = framework_bizobj.MergeLabels( issue.labels, self.parser.labels_add, self.parser.labels_remove, self.config) new_field_values = issue.field_values # TODO(jrobbins): edit custom ones if not allow_edit: # If user can't edit, then only consider the plain-text comment, # and set all other fields back to their original values. logging.info('Processed reply from user who can not edit issue') new_summary = issue.summary new_status = issue.status new_owner_id = issue.owner_id new_cc_ids = issue.cc_ids new_labels = issue.labels new_field_values = issue.field_values amendments, comment_pb = services.issue.ApplyIssueComment( cnxn, services, self.commenter_id, self.project.project_id, issue.local_id, new_summary, new_status, new_owner_id, new_cc_ids, new_labels, new_field_values, issue.component_ids, issue.blocked_on_iids, issue.blocking_iids, issue.dangling_blocked_on_refs, issue.dangling_blocking_refs, issue.merged_into, comment=self.description, inbound_message=self.inbound_message) logging.info('Updated issue %s:%s w/ amendments %r', self.project.project_name, issue.local_id, amendments) if amendments or self.description: # Avoid completely empty comments. send_notifications.PrepareAndSendIssueChangeNotification( issue.issue_id, self.hostport, self.commenter_id, old_owner_id=old_owner_id, comment_id=comment_pb.id)
def testMergeLabels_SingleValuedEnums(self): self.config.field_defs.append(tracker_pb2.FieldDef( field_id=1, field_name='Size', field_type=tracker_pb2.FieldTypes.ENUM_TYPE, is_multivalued=False)) self.config.field_defs.append(tracker_pb2.FieldDef( field_id=1, field_name='Branch-Name', field_type=tracker_pb2.FieldTypes.ENUM_TYPE, is_multivalued=False)) # We can add a label for a single-valued enum. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], ['Size-L'], [], self.config) self.assertEquals(merged_labels, ['Priority-Medium', 'OpSys-OSX', 'Size-L']) self.assertEquals(update_add, ['Size-L']) self.assertEquals(update_remove, []) # Adding and removing the same label adds it. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium'], ['Size-M'], ['Size-M'], self.config) self.assertEquals(merged_labels, ['Priority-Medium', 'Size-M']) self.assertEquals(update_add, ['Size-M']) self.assertEquals(update_remove, []) # Adding Size-L replaces Size-M. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'Size-M'], ['Size-L', 'OpSys-Win'], [], self.config) self.assertEquals(merged_labels, ['Priority-Medium', 'Size-L', 'OpSys-Win']) self.assertEquals(update_add, ['Size-L', 'OpSys-Win']) self.assertEquals(update_remove, []) # Adding Size-L and Size-XL replaces with L only. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Size-M', 'OpSys-OSX'], ['Size-L', 'Size-XL'], [], self.config) self.assertEquals(merged_labels, ['OpSys-OSX', 'Size-L']) self.assertEquals(update_add, ['Size-L']) self.assertEquals(update_remove, []) # Multi-part labels work as expected. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Size-M', 'OpSys-OSX'], ['Size-M-USA'], [], self.config) self.assertEquals( merged_labels, ['OpSys-OSX', 'Size-M-USA']) self.assertEquals(update_add, ['Size-M-USA']) self.assertEquals(update_remove, []) # Multi-part enum names only filter labels that match whole name. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Branch-Name-Master'], ['Branch-Prediction', 'Branch-Name-Beta'], [], self.config) self.assertEquals( merged_labels, ['Branch-Prediction', 'Branch-Name-Beta']) self.assertEquals(update_add, ['Branch-Prediction', 'Branch-Name-Beta']) self.assertEquals(update_remove, [])
def testMergeLabels(self): (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels([], [], [], []) self.assertEquals(merged_labels, []) self.assertEquals(update_add, []) self.assertEquals(update_remove, []) (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels(['a', 'b'], [], [], []) self.assertEquals(merged_labels, ['a', 'b']) self.assertEquals(update_add, []) self.assertEquals(update_remove, []) (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels(['a', 'b', 'd'], ['c'], ['d'], []) self.assertEquals(merged_labels, ['a', 'b', 'c']) self.assertEquals(update_add, ['c']) self.assertEquals(update_remove, ['d']) (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels(['a', 'b', 'd'], ['d'], ['e'], []) self.assertEquals(merged_labels, ['a', 'b', 'd']) self.assertEquals(update_add, []) # d was already there. self.assertEquals(update_remove, []) # there was no e. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], ['Hot'], ['OpSys-OSX'], ['Priority']) self.assertEquals(merged_labels, ['Priority-Medium', 'Hot']) self.assertEquals(update_add, ['Hot']) self.assertEquals(update_remove, ['OpSys-OSX']) (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], ['Priority-High', 'OpSys-Win'], [], ['Priority']) self.assertEquals(merged_labels, ['OpSys-OSX', 'Priority-High', 'OpSys-Win']) self.assertEquals(update_add, ['Priority-High', 'OpSys-Win']) self.assertEquals(update_remove, []) (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], [], ['Priority-Medium', 'OpSys-Win'], ['Priority']) self.assertEquals(merged_labels, ['OpSys-OSX']) self.assertEquals(update_add, []) self.assertEquals(update_remove, ['Priority-Medium'])
def ParseQuickEditCommand(cnxn, cmd, issue, config, logged_in_user_id, services): """Parse a quick edit command into assignments and labels.""" parts = _BreakCommandIntoParts(cmd) parser = AssignmentParser(None, easier_kv_labels=True) for key, value in parts: if key: # A key=value assignment. valid_assignment = parser.ParseAssignment(cnxn, key, value, config, services, logged_in_user_id) if not valid_assignment: logging.info('ignoring assignment: %r, %r', key, value) elif value.startswith('-'): # Removing a label. parser.labels_remove.append(_StandardizeLabel(value[1:], config)) else: # Adding a label. value = value.strip('+') parser.labels_add.append(_StandardizeLabel(value, config)) new_summary = parser.summary or issue.summary if parser.status is None: new_status = issue.status else: new_status = parser.status if parser.owner_id is None: new_owner_id = issue.owner_id else: new_owner_id = parser.owner_id new_cc_ids = [ cc for cc in list(issue.cc_ids) + list(parser.cc_add) if cc not in parser.cc_remove ] (new_labels, _update_add, _update_remove) = framework_bizobj.MergeLabels( issue.labels, parser.labels_add, parser.labels_remove, config) return new_summary, new_status, new_owner_id, new_cc_ids, new_labels
def ParseAssignment(self, cnxn, key, value, config, services, user_id): """Parse command-style text entered by the user to update an issue. E.g., The user may want to set the issue status to "reviewed", or set the owner to "me". Args: cnxn: connection to SQL database. key: string name of the field to set. value: string value to be interpreted. config: Projects' issue tracker configuration PB. services: connections to backends. user_id: int user ID of the user making the change. Returns: True if the line could be parsed as an assigment, False otherwise. Also, as a side-effect, the assigned values are built up in the instance variables of the parser. """ valid_line = True if key == 'owner': if framework_constants.NO_VALUE_RE.match(value): self.owner_id = framework_constants.NO_USER_SPECIFIED else: try: self.owner_id = _LookupMeOrUsername(cnxn, value, services, user_id) except user_svc.NoSuchUserException: logging.warning('bad owner: %r when committing to project_id %r', value, config.project_id) valid_line = False elif key == 'cc': try: add, remove = _ParsePlusMinusList(value) self.cc_add = [_LookupMeOrUsername(cnxn, cc, services, user_id) for cc in add] self.cc_remove = [_LookupMeOrUsername(cnxn, cc, services, user_id) for cc in remove] for user_id in self.cc_add: if user_id not in self.cc_list: self.cc_list.append(user_id) self.cc_list = [user_id for user_id in self.cc_list if user_id not in self.cc_remove] except user_svc.NoSuchUserException: logging.warning('bad cc: %r when committing to project_id %r', value, config.project_id) valid_line = False elif key == 'summary': self.summary = value elif key == 'status': if framework_constants.NO_VALUE_RE.match(value): self.status = '' else: self.status = _StandardizeStatus(value, config) elif key == 'label' or key == 'labels': self.labels_add, self.labels_remove = _ParsePlusMinusList(value) self.labels_add = [_StandardizeLabel(lab, config) for lab in self.labels_add] self.labels_remove = [_StandardizeLabel(lab, config) for lab in self.labels_remove] (self.labels_list, _update_add, _update_remove) = framework_bizobj.MergeLabels( self.labels_list, self.labels_add, self.labels_remove, config.exclusive_label_prefixes) elif (self.easier_kv_labels and key not in tracker_constants.RESERVED_PREFIXES and key and value): if key.startswith('-'): self.labels_remove.append(_StandardizeLabel( '%s-%s' % (key[1:], value), config)) else: self.labels_add.append(_StandardizeLabel( '%s-%s' % (key, value), config)) else: valid_line = False return valid_line
def testMergeLabels_Labels(self): # Empty case. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( [], [], [], self.config) self.assertEquals(merged_labels, []) self.assertEquals(update_add, []) self.assertEquals(update_remove, []) # No-op case. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['a', 'b'], [], [], self.config) self.assertEquals(merged_labels, ['a', 'b']) self.assertEquals(update_add, []) self.assertEquals(update_remove, []) # Adding and removing at the same time. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['a', 'b', 'd'], ['c'], ['d'], self.config) self.assertEquals(merged_labels, ['a', 'b', 'c']) self.assertEquals(update_add, ['c']) self.assertEquals(update_remove, ['d']) # Removing a non-matching label has no effect. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['a', 'b', 'd'], ['d'], ['e'], self.config) self.assertEquals(merged_labels, ['a', 'b', 'd']) self.assertEquals(update_add, []) # d was already there. self.assertEquals(update_remove, []) # there was no e. # We can add and remove at the same time. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], ['Hot'], ['OpSys-OSX'], self.config) self.assertEquals(merged_labels, ['Priority-Medium', 'Hot']) self.assertEquals(update_add, ['Hot']) self.assertEquals(update_remove, ['OpSys-OSX']) # Adding Priority-High replaces Priority-Medium. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], ['Priority-High', 'OpSys-Win'], [], self.config) self.assertEquals(merged_labels, ['OpSys-OSX', 'Priority-High', 'OpSys-Win']) self.assertEquals(update_add, ['Priority-High', 'OpSys-Win']) self.assertEquals(update_remove, []) # Adding Priority-High and Priority-Low replaces with High only. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], ['Priority-High', 'Priority-Low'], [], self.config) self.assertEquals(merged_labels, ['OpSys-OSX', 'Priority-High']) self.assertEquals(update_add, ['Priority-High']) self.assertEquals(update_remove, []) # Removing a mix of matching and non-matching labels only does matching. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], [], ['Priority-Medium', 'OpSys-Win'], self.config) self.assertEquals(merged_labels, ['OpSys-OSX']) self.assertEquals(update_add, []) self.assertEquals(update_remove, ['Priority-Medium']) # Multi-part labels work as expected. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX-11'], ['Priority-Medium-Rare', 'OpSys-OSX-13'], [], self.config) self.assertEquals( merged_labels, ['OpSys-OSX-11', 'Priority-Medium-Rare', 'OpSys-OSX-13']) self.assertEquals(update_add, ['Priority-Medium-Rare', 'OpSys-OSX-13']) self.assertEquals(update_remove, []) # Multi-part exclusive prefixes only filter labels that match whole prefix. self.config.exclusive_label_prefixes.append('Branch-Name') (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Branch-Name-Master'], ['Branch-Prediction', 'Branch-Name-Beta'], [], self.config) self.assertEquals( merged_labels, ['Branch-Prediction', 'Branch-Name-Beta']) self.assertEquals(update_add, ['Branch-Prediction', 'Branch-Name-Beta']) self.assertEquals(update_remove, [])
def testMergeLabels_MultiValuedEnums(self): self.config.field_defs.append(tracker_pb2.FieldDef( field_id=1, field_name='OpSys', field_type=tracker_pb2.FieldTypes.ENUM_TYPE, is_multivalued=True)) self.config.field_defs.append(tracker_pb2.FieldDef( field_id=1, field_name='Branch-Name', field_type=tracker_pb2.FieldTypes.ENUM_TYPE, is_multivalued=True)) # We can add a label for a multi-valued enum. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium'], ['OpSys-Win'], [], self.config) self.assertEquals(merged_labels, ['Priority-Medium', 'OpSys-Win']) self.assertEquals(update_add, ['OpSys-Win']) self.assertEquals(update_remove, []) # We can remove a matching label for a multi-valued enum. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-Win'], [], ['OpSys-Win'], self.config) self.assertEquals(merged_labels, ['Priority-Medium']) self.assertEquals(update_add, []) self.assertEquals(update_remove, ['OpSys-Win']) # We can remove a non-matching label and it is a no-op. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], [], ['OpSys-Win'], self.config) self.assertEquals(merged_labels, ['Priority-Medium', 'OpSys-OSX']) self.assertEquals(update_add, []) self.assertEquals(update_remove, []) # Adding and removing the same label adds it. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium'], ['OpSys-Win'], ['OpSys-Win'], self.config) self.assertEquals(merged_labels, ['Priority-Medium', 'OpSys-Win']) self.assertEquals(update_add, ['OpSys-Win']) self.assertEquals(update_remove, []) # We can add a label for a multi-valued enum, even if matching exists. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Priority-Medium', 'OpSys-OSX'], ['OpSys-Win'], [], self.config) self.assertEquals( merged_labels, ['Priority-Medium', 'OpSys-OSX', 'OpSys-Win']) self.assertEquals(update_add, ['OpSys-Win']) self.assertEquals(update_remove, []) # Adding two at the same time is fine. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Size-M', 'OpSys-OSX'], ['OpSys-Win', 'OpSys-Vax'], [], self.config) self.assertEquals( merged_labels, ['Size-M', 'OpSys-OSX', 'OpSys-Win', 'OpSys-Vax']) self.assertEquals(update_add, ['OpSys-Win', 'OpSys-Vax']) self.assertEquals(update_remove, []) # Multi-part labels work as expected. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Size-M', 'OpSys-OSX'], ['OpSys-Win-10'], [], self.config) self.assertEquals( merged_labels, ['Size-M', 'OpSys-OSX', 'OpSys-Win-10']) self.assertEquals(update_add, ['OpSys-Win-10']) self.assertEquals(update_remove, []) # Multi-part enum names don't mess up anything. (merged_labels, update_add, update_remove) = framework_bizobj.MergeLabels( ['Branch-Name-Master'], ['Branch-Prediction', 'Branch-Name-Beta'], [], self.config) self.assertEquals( merged_labels, ['Branch-Name-Master', 'Branch-Prediction', 'Branch-Name-Beta']) self.assertEquals(update_add, ['Branch-Prediction', 'Branch-Name-Beta']) self.assertEquals(update_remove, [])