def run_status(self, ticket_id, status=None): """Updates the status of a ticket. usage: cm status ticket_id [new_status] """ ticket_id = text.validate_id(ticket_id) self.login() # Get all the available actions for this ticket r = self.get("/ticket/{}".format(ticket_id)) timestamps = self._extract_timestamps(r.text) statuses = text.extract_statuses(r.text) # A ``status`` was provided, try to find the exact match, else just # display the current status for this ticket, and the available ones. if status: status = text.fuzzy_find(status, statuses) if not status: raise exceptions.FatalError("bad status (for this ticket: {})" .format(", ".join(statuses))) else: status = text.extract_status_from_ticket_page(r.text) print("Current status: {}".format(status)) print("Available statuses: {}".format(", ".join(statuses))) return if self.message: comment = self.message elif self.add_comment: comment = self._read_comment() else: comment = "" data = { "action": status, "comment": comment, } data.update(timestamps) r = self.post("/ticket/{}".format(ticket_id), data) if "system-message" in r.text or r.status_code != 200: raise exceptions.FatalError("unable to set status")
def run_new(self, owner=None): """Create a new ticket and return its id if successful. usage: cm new [owner] """ template = self.resolve_template() if not template: template = DEFAULT_TEMPLATE # Parse the template ahead of time, allowing us to insert the Owner/To. ep = email.parser.Parser() em = ep.parsestr(template) body = em.get_payload() headers = OrderedDict(em.items()) # The owner specified on the command line always prevails. if owner: headers["To"] = owner # If all else fail, assign it to yourself. if not headers["To"]: headers["To"] = self.username valid = False while not valid: # Get the properties at each iteration, in case an admin updated # the list in the mean time. properties = self.get_properties() # Assume the user will produce a valid ticket valid = True # Load the current values in a temp file for editing (fd, filename) = tempfile.mkstemp(suffix=".cm.ticket") fp = os.fdopen(fd, "w") fp.write(self._format_headers(headers)) fp.write("\n\n") fp.write(body) fp.close() self.editor(filename) # Use the email parser to get the headers. ep = email.parser.Parser() with open(filename, "r") as fp: em = ep.parse(fp) os.unlink(filename) body = em.get_payload() headers = OrderedDict(em.items()) errors = [] fuzzy_match_fields = ("Milestone", "Component", "Type", "Version", "Priority") # Ensures all the required fields are filled-in for key in self.required_fields: if key in fuzzy_match_fields: continue if not headers.get(key) or "**ERROR**" in headers[key]: errors.append("Invalid '{}': cannot be blank".format(key)) # Some fields are tolerant to incomplete values, this is where we # try to complete them. for key in fuzzy_match_fields: lkey = key.lower() if lkey not in properties: continue valid_options = properties[lkey]["options"] # The specified value is not available in the multi-choice. if key in headers and headers[key] not in valid_options: m = text.fuzzy_find(headers[key], valid_options) if m: # We found a close match, update the value with it. headers[key] = m else: # We didn't find a close match. If the user entered # something explicitly or if this field is required, # this is an error, else just wipe the value and move # on. if headers[key] or key in self.required_fields: joined_options = ", ".join(valid_options) errors.append(u"Invalid '{}': expected: {}" .format(key, joined_options)) else: headers[key] = "" if errors: valid = False print("\nFound the following errors:") for error in errors: print(u" - {}".format(error)) try: self.input("\n-- Hit Enter to return to editor, "\ "^C to abort --\n") except KeyboardInterrupt: raise exceptions.FatalError("ticket creation interrupted") # Since the body is expected to be using CRLF line termination, we # replace newlines by CRLF if no CRLF is found. if "\r\n" not in body: body = body.replace("\n", "\r\n") fields_data = { "field_summary": headers.get("Subject", ""), "field_type": headers.get("Type", ""), "field_version": headers.get("Version", ""), "field_description": body, "field_milestone": headers.get("Milestone", ""), "field_component": headers.get("Component", ""), "field_owner": headers.get("To", ""), "field_keywords": headers.get("Keywords", ""), "field_cc": headers.get("Cc", ""), "field_attachment": "", } # Assume anything outside of the original headers it to be included as # fields. for key, value in headers.items(): field_name = "field_" + key.lower() if field_name not in fields_data: fields_data[field_name] = value r = self.post("/newticket", fields_data) if r.status_code != 200: message = text.extract_message(r.text) if not message: message = "unable to create new ticket" raise exceptions.RequestException(message) try: ticket_id = int(r.url.split("/")[-1]) except: raise exceptions.RequestException("returned ticket_id is invalid.") self.open_in_browser_on_request(ticket_id) return ["ticket #{} created".format(ticket_id)]
def test_fuzzy_find_two_one_match_re(self): self.assertEquals(text.fuzzy_find("meh", ["meh stuff", "mih stuff"]), "meh stuff")
def test_fuzzy_find_two_one_match(self): self.assertEquals(text.fuzzy_find("meh", ["megl", "mehl"]), "mehl")
def test_fuzzy_find_one_letter_diff(self): self.assertEquals(text.fuzzy_find("meh", ["meg"]), "meg")
def test_fuzzy_find_same_option(self): self.assertEquals(text.fuzzy_find("meh", ["meh"]), "meh")
def test_fuzzy_find_bad_option(self): self.assertEquals(text.fuzzy_find("meh", ["bad"]), None)
def test_fuzzy_find_no_options(self): self.assertEquals(text.fuzzy_find("meh", []), None)
def run_new(self, owner=None): """Create a new ticket and return its id if successful. usage: cm new [owner] """ template = self.resolve_template() if not template: if self.message_file: if self.message_file == "-": template = sys.stdin.read() else: with open(self.message_file) as fp: template = fp.read() else: template = DEFAULT_TEMPLATE # Parse the template ahead of time, allowing us to insert the Owner/To. ep = email.parser.Parser() em = ep.parsestr(template) body = em.get_payload() headers = OrderedDict(em.items()) # The owner specified on the command line always prevails. if owner: headers["To"] = owner # If all else fail, assign it to yourself. if not headers["To"]: headers["To"] = self.username self.login() valid = False while not valid: # Get the properties at each iteration, in case an admin updated # the list in the mean time. options = self.get_property_options() # Assume the user will produce a valid ticket valid = True # Load the current values in a temp file for editing (fd, filename) = tempfile.mkstemp(suffix=".cm.ticket") fp = os.fdopen(fd, "w") fp.write(self._format_headers(headers)) fp.write("\n\n") fp.write(body) fp.close() # When reading the message from stdin, we can't edit, skip editor. if not self.message_file: self.editor(filename) # Use the email parser to get the headers. ep = email.parser.Parser() with open(filename, "r") as fp: em = ep.parse(fp) os.unlink(filename) body = em.get_payload() headers = OrderedDict(em.items()) errors = [] fuzzy_match_fields = ("Milestone", "Component", "Type", "Version", "Priority") # Ensures all the required fields are filled-in for key in self.required_fields: if key in fuzzy_match_fields: continue if not headers.get(key) or "**ERROR**" in headers[key]: errors.append("Invalid '{}': cannot be blank".format(key)) # Some fields are tolerant to incomplete values, this is where we # try to complete them. for key in fuzzy_match_fields: lkey = key.lower() if lkey not in options: continue valid_options = options[lkey] # The specified value is not available in the multi-choice. if key in headers and headers[key] not in valid_options: m = text.fuzzy_find(headers[key], valid_options) if m: # We found a close match, update the value with it. headers[key] = m else: # We didn't find a close match. If the user entered # something explicitly or if this field is required, # this is an error, else just wipe the value and move # on. if headers[key] or key in self.required_fields: joined_options = ", ".join(valid_options) errors.append(u"Invalid '{}': expected: {}".format( key, joined_options)) else: headers[key] = "" if errors: valid = False print("\nFound the following errors:") for error in errors: print(u" - {}".format(error)) try: if not self.message_file: self.input("\n-- Hit Enter to return to editor, " "^C to abort --\n") except KeyboardInterrupt: raise exceptions.FatalError("ticket creation interrupted") # There is no editor loop when reading message from stdin, just # print the errors and exit. if self.message_file: break # Since the body is expected to be using CRLF line termination, we # replace newlines by CRLF if no CRLF is found. if "\r\n" not in body: body = body.replace("\n", "\r\n") fields_data = { "field_summary": headers.get("Subject", ""), "field_type": headers.get("Type", ""), "field_version": headers.get("Version", ""), "field_description": body, "field_milestone": headers.get("Milestone", ""), "field_component": headers.get("Component", ""), "field_owner": headers.get("To", ""), "field_keywords": headers.get("Keywords", ""), "field_cc": headers.get("Cc", ""), "field_attachment": "", } # Assume anything outside of the original headers it to be included as # fields. for key, value in headers.items(): field_name = "field_" + key.lower() if field_name not in fields_data: fields_data[field_name] = value r = self.post("/newticket", fields_data) if r.status_code != 200: message = text.extract_message(r.text) if not message: message = "unable to create new ticket" raise exceptions.RequestException(message) try: ticket_id = int(r.url.split("/")[-1]) except: raise exceptions.RequestException("returned ticket_id is invalid.") self.open_in_browser_on_request(ticket_id) return ["ticket #{} created".format(ticket_id)]