Using Python to update a required field while performing a transition in Jira

'Gojira!' by donsolo

This might be a very esoteric topic for most people, but since I could not find information about this anywhere, I decided to document this in a post.

Here is the problem. I use Jira at work, and today, I needed to close a bunch of tickets based on a search result. Now, searching or doing batch operations is simple enough from the browser, but a small detail made the exercise impossible via the web UI.

Our Jira project requires a field to be filled while closing the ticket - the time spent in the ticket. This breaks Jira in all sorts of ways - the batch operation doesn’t work, some of the email-to-jira interface at work breaks as well.

So I looked at doing this via the Jira Python library. But it didn’t work as expected.

>>> from jira import JIRA

>>> jira = JIRA("https://JIRA_URL", basic_auth=('USER_NAME', 'Password'))
>>>
>>> issue = jira.issue("ISSUE-123")
>>>
>>> [(t['id'], t['name']) for t in jira.transitions(issue)]  # What are the workflows available?
[(u'4', u'Start Progress'), (u'5', u'Resolve Issue'), (u'2', u'Close Issue'), (u'711', u'Planning'), (u'751', u'Blocked'), (u'801', u'To Monitor')
>>>
>>> jira.transition_issue(issue, '2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
...
jira.exceptions.JIRAError: JiraError HTTP 400
    text: Time Spent is required
    url: ...
    response headers = {...}
    response text = {"errorMessages":["Time Spent is required"],"errors":{}}

The documentation on transitions mentioned that we could add fields in the call to jira.transitions(). That didn’t work as well.

>>> jira.transition_issue(issue, '2', timespent="1h")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
...
jira.exceptions.JIRAError: JiraError HTTP 400
    text: Field 'timespent' cannot be set. It is not on the appropriate screen, or unknown.
    url: ...
    response headers = ...
    response text = {"errorMessages":[],"errors":{"timespent":"Field 'timespent' cannot be set. It is not on the appropriate screen, or unknown."}}

So I scoured the Internet for a long time, till I found this post about how to do it via the REST API - the only official interface to Jira. Here is what needs to be sent as the body to the POST request.

{
    "transition": {
        "id": "2"
    },
    "update": {
        "worklog": [
            {
                "add": {
                    "timeSpent": "2m"
                }
            }
        ]
    }
}

I felt that to be odd, till I looked at both the api documentation for transition and the doc for transition using the python library and I found out why I have not been successful till now.

The REST api supports two ways to update the issue while doing a transition - you can set certain fields using the fields option, or you can use the update option to do more complex changes.

The comment in the Python code revealed that the update method has not yet been implemented.

def transition_issue(self, issue, transition, fields=None, comment=None, **fieldargs):

    # TODO: Support update verbs (same as issue.update())

That put me in a bind. I had only one way to hack around this problem now - using the REST api for the specific operation I wanted, and the Python library for the rest of the work - ugly, but works for now, till the Python library is complete.

So here was the final solution that did what I wanted.

from jira import JIRA
import requests

jira = JIRA("https://JIRA_URL", basic_auth=('USER_NAME', 'PASSWORD'))

d = {}
d["transition"]={"id": "2"}
d["update"]={"worklog": [{"add": {"timeSpent": "1h"}}]}

s = requests.Session()
s.auth = ("USER", "PASSWORD")
s.headers.update({"Content-Type": "application/json"})

j="https://JIRA_URL"

issue_list = jira.search_issues("assignee = currentUser() AND resolution = Unresolved  and status != Closed and updatedDate < '2015-10-01' and project='PROJECT' ORDER BY updatedDate DESC")

for i in issue_list:
    print i
    s.post(j+"/rest/api/2/issue/"+i.key+"/transitions", data=json.dumps(d))
techprogrammingpython
German cycle superhighway opens its first stretch Documenting both class and constructor in Sphinx