Example #1
0
    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")
Example #2
0
File: app.py Project: strk/cartman
    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)]
Example #3
0
 def test_fuzzy_find_two_one_match_re(self):
     self.assertEquals(text.fuzzy_find("meh", ["meh stuff", "mih stuff"]),
                       "meh stuff")
Example #4
0
 def test_fuzzy_find_two_one_match(self):
     self.assertEquals(text.fuzzy_find("meh", ["megl", "mehl"]), "mehl")
Example #5
0
 def test_fuzzy_find_one_letter_diff(self):
     self.assertEquals(text.fuzzy_find("meh", ["meg"]), "meg")
Example #6
0
 def test_fuzzy_find_same_option(self):
     self.assertEquals(text.fuzzy_find("meh", ["meh"]), "meh")
Example #7
0
 def test_fuzzy_find_bad_option(self):
     self.assertEquals(text.fuzzy_find("meh", ["bad"]), None)
Example #8
0
 def test_fuzzy_find_no_options(self):
     self.assertEquals(text.fuzzy_find("meh", []), None)
Example #9
0
 def test_fuzzy_find_two_one_match_re(self):
     self.assertEquals(text.fuzzy_find("meh", ["meh stuff", "mih stuff"]),
                       "meh stuff")
Example #10
0
    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)]
Example #11
0
 def test_fuzzy_find_two_one_match(self):
     self.assertEquals(text.fuzzy_find("meh", ["megl", "mehl"]), "mehl")
Example #12
0
 def test_fuzzy_find_one_letter_diff(self):
     self.assertEquals(text.fuzzy_find("meh", ["meg"]), "meg")
Example #13
0
 def test_fuzzy_find_same_option(self):
     self.assertEquals(text.fuzzy_find("meh", ["meh"]), "meh")
Example #14
0
 def test_fuzzy_find_bad_option(self):
     self.assertEquals(text.fuzzy_find("meh", ["bad"]), None)
Example #15
0
 def test_fuzzy_find_no_options(self):
     self.assertEquals(text.fuzzy_find("meh", []), None)