How to customize Jira plugin to include other fields (example: components) in Create Issue task

Follow

Kenny Lim -

To add a Component field:

  • Edit your ext/synthetic.xml, which defines the Create Issue (what properties/fields the task should have):
<?xml version='1.0' encoding='UTF-8'?>
<synthetic xsi:schemaLocation="http://www.xebialabs.com/deployit/synthetic synthetic.xsd" xmlns="http://www.xebialabs.com/deployit/synthetic" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <type-modification type="jira.CreateIssue">
        <property name="project" category="input" description="JIRA project in which to create the issue"/>
        <property name="title" category="input" description="Issue title"/>
        <property name="description" size="large" category="input" required="false" description="Issue description"/>
        <property name="issueType" category="input" default="Task" description="Issue type (for example, Bug, Improvement, or Feature)"/>
        <property name="componentList" category="input" kind="list_of_string" description="Component type (for example, Settings, User Interface, or Security)"/>

        <property name="issueId" category="output" required="false" description="ID of the created issue"/>
    </type-modification>
</synthetic>

where componentList is the additional field we want, and it is a string list kind because a Jira ticket can have multiple components. I did not set a default value as the component list differ across Jira Projects. Do note that this is a field so make sure users know exactly what components are there for the project they are creating issue on, or else Jira will have problem matching the components and the task will fail.

  • Create a folder ext/jira and create a file named __init__.py and CreateIssue.py
  • contents of CreateIssue.py:
from jira import JiraServer

jira = JiraServer(jiraServer, username, password)

issueId = jira.createIssue(project, title, description, issueType, componentList)

task.setStatusLine("Created %s" % (jira.link(issueId)))
  • and contents of __init__.py:
import string
import com.xhaus.jyson.JysonCodec as Json
from util import error
from xlrelease.HttpRequest import HttpRequest


class JiraServer:

    def __init__(self, jira_server, username, password, encoding='utf-8'):
        if jira_server is None:
            error('No server provided.')

        self.jira_server = jira_server
        self.username = username
        self.password = password
        self.encoding = encoding

    def queryIssues(self, query, options=None):

        if not query:
            error('No JQL query provided.')

        # Create POST body
        content = {
            'jql': query,
            'startAt': 0,
            'fields': ['summary', 'status', 'assignee'],
            'maxResults': 1000
        }
        # Do request
        request = self._createRequest()
        response = request.post('/rest/api/2/search', self._serialize(content), contentType='application/json')
        # Parse result
        if response.status == 200:
            issues = {}
            data = Json.loads(response.response)
            for item in data['issues']:
                issue = item['key']
                assignee = "Unassigned" if (item['fields']['assignee'] is None) else item['fields']['assignee']['displayName']
                issues[issue] = {
                    'issue'   : issue,
                    'summary' : item['fields']['summary'],
                    'status'  : item['fields']['status']['name'],
                    'assignee': assignee,
                    'link'    : "{1}/browse/{0}".format(issue, self.jira_server['url'])
                }
            return issues
        else:
            error(u"Failed to execute search '{0}' in JIRA.".format(query), response)

    def query(self, query):
        if not query:
            error('No JQL query provided.')

        # Create POST body
        content = {
            'jql': query,
            'startAt': 0,
            'fields': ['summary'],
            'maxResults': 1000
        }

        # Do request
        request = self._createRequest()
        response = request.post('/rest/api/2/search', self._serialize(content), contentType='application/json')
        # Parse result
        if response.status == 200:
            data = Json.loads(response.response)

            print "#### Issues found"
            issues = {}
            for item in data['issues']:
                issue = item['key']
                issues[issue] = item['fields']['summary']
                print u"* {0} - {1}".format(self.link(issue), item['fields']['summary'])
            print "\n"
            return issues

        else:
            error(u"Failed to execute search '{0}' in JIRA.".format(query), response)

    def createIssue(self, project, title, description, issue_type, componentList):
        # Create POST body
        components = []
        for value in componentList:
            component = {}
            component['name']=value
            components.append(component)
        content = {
            'fields': {
                'project': {
                    'key': project
                },
                'summary': title,
                'description': description,
                'issuetype': {
                    'name': string.capwords(issue_type)
                },
                'components': components
            }
        }

        # Do request
        request = self._createRequest()
        response = request.post('/rest/api/2/issue', self._serialize(content), contentType='application/json')

        # Parse result
        if response.status == 201:
            data = Json.loads(response.response)
            issue_id = data.get('key')
            print u"Created {0} in JIRA.".format(self.link(issue_id))
            return issue_id
        else:
            error("Failed to create issue in JIRA.", response)

    def updateIssue(self, issue_id, new_status, comment, new_summary=None):
        # Check for ticket
        self._checkIssue(issue_id)

        # Status transition
        if new_status:
            self._transitionIssue(issue_id, new_status, new_summary)
            if comment:
                self._updateComment(issue_id, comment)
        else:
            self._updateIssue(issue_id, new_summary, comment)

        print u"Updated {0}".format(self.link(issue_id))
        print

    ######

    def _getUpdatedIssueData(self, summary, comment):
        updated_data = {}

        if comment:
            updated_data.update({
                "comment": [
                    {
                        "add": {
                            "body": comment
                        }
                    }
                ]
            })

        if summary is not None:
            updated_data.update({
                "summary": [
                    {
                        "set": summary
                    }
                ]
            })

        return updated_data

    def _updateComment(self, issue_id, comment):
        request = self._createRequest()
        response = request.post(self._issueUrl(issue_id) + "/comment", self._serialize({"body": comment}), contentType='application/json')

        if response.status != 201:
            error(u"Unable to comment issue {0}. Make sure you have 'Add Comments' permission.".format(self.link(issue_id)), response)

    def _updateIssue(self, issue_id, new_summary, comment):

        updated_data = self._getUpdatedIssueData(new_summary, comment)

        # Create POST body
        request_data = {"update": updated_data}

        # Do request
        request = self._createRequest()
        response = request.put(self._issueUrl(issue_id), self._serialize(request_data), contentType='application/json')

        # Parse result
        if response.status != 204:
            error(u"Unable to update issue {0}. Please make sure the issue is not in a 'closed' state.".format(self.link(issue_id)), response)

    def _transitionIssue(self, issue_id, new_status, summary):

        issue_url = self._issueUrl(issue_id)

        # Find possible transitions
        request = self._createRequest()
        response = request.get(issue_url + "/transitions?expand=transitions.fields", contentType='application/json')

        if response.status != 200:
            error(u"Unable to find transitions for issue {0}".format(self.link(issue_id)), response)

        transitions = Json.loads(response.response)['transitions']

        # Check  transition
        wanted_transaction = -1
        for transition in transitions:
            if transition['to']['name'].lower() == new_status.lower():
                wanted_transaction = transition['id']
                break

        if wanted_transaction == -1:
            error(u"Unable to find status {0} for issue {1}".format(new_status, self.link(issue_id)))

        # Prepare POST body
        transition_data = {
            "transition": {
                "id": wanted_transaction
            }
        }

        if summary is not None:
            transition_data.update({
                "fields": {
                    "summary": summary
                }
            })

        # Perform transition
        response = request.post(issue_url + "/transitions?expand=transitions.fields", self._serialize(transition_data), contentType='application/json')

        if response.status != 204:
            error(u"Unable to perform transition {0} for issue {1}".format(wanted_transaction, self.link(issue_id)), response)

    def _createRequest(self):
        return HttpRequest(self.jira_server, self.username, self.password)

    def link(self, issue_id):
        return "[{0}]({1}/browse/{0})".format(issue_id, self.jira_server['url'])

    def _issueUrl(self, issue_id):
        return "/rest/api/2/issue/" + issue_id

    def _checkIssue(self, issue_id):
        request = self._createRequest()
        response = request.get(self._issueUrl(issue_id), contentType='application/json')

        if response.status != 200:
            error(u"Unable to find issue {0}".format(self.link(issue_id)), response)

    def _serialize(self, content):
        return Json.dumps(content).encode(self.encoding)

 

Basically for CreateIssue.py, I modified line 5 to include the new componentList list. But note that it uses another function jira.createIssue from class JiraServer which is from __init__.py.

 

For __init__.py, I modified the createIssue function:

    def createIssue(self, project, title, description, issue_type, componentList):
        # Create POST body
        components = []
        for value in componentList:
            component = {}
            component['name']=value
            components.append(component)
        content = {
            'fields': {
                'project': {
                    'key': project
                },
                'summary': title,
                'description': description,
                'issuetype': {
                    'name': string.capwords(issue_type)
                },
                'components': components
            }
        }

so that it reads the componentList from the task field, then converts it into a valid value (an array) for Jira using

        components = []
        for value in componentList:
            component = {}
            component['name']=value
            components.append(component)

and later referenced as components in the content (body of the request sent to Jira via REST).

By doing these changes, and restarting your XL Release, you should now be able to create a Jira task with components defined.

Have more questions? Submit a request