Basic Jira REST Operations

I started extracting some Jira data a few weeks ago using Jira’s REST Java client. However, sometimes I found it difficult to get the right information, though there is some documentation on the REST client. But there are a lot of different client versions and it’s not always clear which version the documentation refers to. The client’s code is not always well documented, at least the code I’ve looked at. So all this made it sometimes pretty difficult for me to figure out how to do things right and how to get started.
Of course I got a lot of help from Atlassian Answers. Thanks to all the folks providing good tips and answers there! The Answers-community made it possible for me to get a few things up and running. However, the answers are scattered and often refer to different versions of the REST client. So I thought and I hope it is a good idea to share my gathered knowledge and provide some out of the box examples here. Hopefully they will be useful for others, too.
The examples here cover Jira REST client version 2.0.0-m2. They may also work with 2.0.0-m1, but I did not test this. I’m pretty sure they will not work with any 1.x version.
What I did was to implement these use cases which I will cover in more detail:

  1. Initializing a connection to Jira
  2. Retrieving an existing issue
  3. Creating a new Jira issue
  4. Adding a comment to an existing issue
  5. Transitioning of an existing issue

I will show only code excerpts. But they should be sufficient for anyone being familiar with Java.

Initializing a connection to Jira

The first code snippet shows a method named init() which actually performs connection initialization.
Also shown in this snippet are all package imports which apply for all the following examples, too. The getter and setter methods should be self-explanatory.

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.concurrent.ExecutionException;

import org.apache.log4j.Logger;

import com.atlassian.jira.rest.client.IssueRestClient;
import com.atlassian.jira.rest.client.JiraRestClient;
import com.atlassian.jira.rest.client.JiraRestClientFactory;
import com.atlassian.jira.rest.client.domain.BasicIssue;
import com.atlassian.jira.rest.client.domain.Comment;
import com.atlassian.jira.rest.client.domain.Issue;
import com.atlassian.jira.rest.client.domain.Transition;
import com.atlassian.jira.rest.client.domain.input.IssueInput;
import com.atlassian.jira.rest.client.domain.input.IssueInputBuilder;
import com.atlassian.jira.rest.client.domain.input.TransitionInput;
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
import com.atlassian.util.concurrent.Promise;

    /**
     * Initialize connection to Jira.
     */
    private void init() {

        URI uri = null;
        JiraRestClientFactory factory = null;

        try {

            uri = new URI(getJiraUrl());
            LOG.debug("Trying to access " + uri);

            factory = new AsynchronousJiraRestClientFactory();
            jiraClient = factory.createWithBasicHttpAuthentication(uri, getUser(), getPassword());
            setJiraClient(jiraClient);
            LOG.debug("Initialized Jira client " + getJiraClient());

        } catch (URISyntaxException e) {

            throw new JiraClientException("Could not establish connection to " + getJiraUrl(), e);
        }
    }

Retrieving an existing issue

All the following examples shown rely on a method getPlainJiraIssue. This method actually retrieves an existing issue from Jira. It simply takes an issue ID as the only argument and uses then the IssueRestClient to retrieve the issue.

 
    /**
     * Get the Jira issue defined by the given ID.
     * 
     * @param issueId The issue's ID.
     * @return The Jira issue.
     */
    private Issue getPlainJiraIssue(String issueId) throws InterruptedException, ExecutionException {

        IssueRestClient issueClient = null;
        Issue issue = null;
        Promise<Issue> promise = null;

        issueClient = getJiraClient().getIssueClient();
        promise = issueClient.getIssue(issueId);
        issue = promise.get();

        return issue;
    }

The Promise type actually extends from java.util.concurrent.Future, which represents the result of asynchronous computations. Have a look at the Javadocs of this type for further information.

Creating a new Jira issue

For creating a new issue you will need to do some preparation steps. You should also focus on method checkMandatoryFieldsForCreatingIssue since here one can see what is necessary to create a new Jira issue. You may simply ignore class IssueInterface which is the return type of method createJiraIssue. This is just a Java bean I’m using to perform some more data transformations. However, the getter methods should show you what is required.

    public IssueInterface createJiraIssue(IssueInterface data) {

        IssueInterface newIssueInt = null;
        IssueInputBuilder builder = null;
        IssueInput issueInput = null;
        Promise<BasicIssue> basicPromise = null;
        Promise<Issue> promise = null;
        BasicIssue basicIssue = null;
        Issue issue = null;

        try {
            checkMandatoryFieldsForCreatingIssue(data);

            builder = new IssueInputBuilder(data.getJiraProjectkey(), Long.valueOf(data.getJiraIssuetype()));
            builder.setSummary(data.getTitle());
            builder.setDescription(data.getDescription());

            issueInput = builder.build();
            basicPromise = getJiraClient().getIssueClient().createIssue(issueInput);
            basicIssue = basicPromise.get();
            promise = getJiraClient().getIssueClient().getIssue(basicIssue.getKey());
            issue = promise.get();
            LOG.debug("New Jira issue created with ID: " + issue.getKey());

            newIssueInt = InterfaceMapper.convertIssue(issue);
            LOG.debug("Converted issue to interface.");

        } catch (Exception e) {

            throw new JiraClientException("Could not create new Jira issue!", e);
        }

        return newIssueInt;
    }

    /**
     * Check if all fields necessary for creating a new Jira issue are available. Method will throw appropriate runtime exceptions
     * if fields or values are missing.
     * 
     * @param issueInt The data containing the values for the new issue.
     */
    void checkMandatoryFieldsForCreatingIssue(IssueInterface issueInt) {

        if (issueInt.getJiraProjectkey() == null || issueInt.getJiraProjectkey().isEmpty())
            throw new JiraClientException("For creating an new issue the project key must be available!");

        if (issueInt.getJiraIssuetype() <= 0L)
            throw new JiraClientException("For creating an new issue the issue type must be available!");

        if (issueInt.getTitle() == null || issueInt.getTitle().isEmpty())
            throw new JiraClientException("For creating an new issue the summary (title) must be available!");
    }

Adding a comment to an existing issue

The method addComment takes two arguments: The ID of an existing Jira issue and the comment to be added. What happens then is to get the existing issue from Jira (method getPlainJiraIssue). Once the issue is retrieved you will have its URI. And along with the URI and the IssueRestClient, simply add the new comment. Beware that you have to append the string “/comment/” to the issue’s URL.

 

    public IssueInterface addComment(String issueId, String comment) {

        IssueInterface issueInt = null;
        Issue issue = null;
        URI issueURI = null;
        Comment jiraComment = null;
        Promise<Void> promise = null;
        IssueRestClient issueClient = null;

        try {

            LOG.debug("Try to add comment for issue " + issueId);
            issue = getPlainJiraIssue(issueId);
            issueURI = new URI(issue.getSelf().toString() + "/comment/");
            jiraComment = Comment.valueOf(comment);
            issueClient = getJiraClient().getIssueClient();
            promise = issueClient.addComment(issueURI, jiraComment);
            promise.claim();
            LOG.debug("Comment added to issue " + issueId);

            issue = getPlainJiraIssue(issueId);
            issueInt = InterfaceMapper.convertIssue(issue);

        } catch (Exception e) {

            throw new JiraClientException("Could not add comment to Jira issue " + issueId, e);
        }

        return issueInt;
    }


Transitioning of an existing issue

Most important thing when trying to transition an issue is to know the transition IDs which are allowed for this particular issue. As far as I have seen (not sure therefore) the issue ID will vary depending on the workflow, the status types and – of course – the transitions in a Jira project. Therefore it’s important to check if a transition applies to an issue at all!

So basically the code shown in this example will not be sufficient for most use cases since user interaction is required to

  • provide possible transitions and
  • let the user decide which transition to perform.

That’s what Jira is actually providing via its GUI. If you want to reuse the code snippet below, you will also have to enhance it in order to do some user interaction. (At the time writing this post, that’s what I will also have to do.)

As a “replacement” for user interaction, the implementation shown in this example performs these steps:

  1. get the Jira issue using its ID (line 10)
  2. get the available transition IDs (line 11+12)
  3. check if the requested transition is allowed (line 14 / 37)
  4. if yes, perform the transition (line 16+17)
 

    public void transitionJiraIssue(String issueId, int transitionId) {

        Issue issue = null;
        Promise<Iterable<Transition>> promise = null;
        Iterator<Transition> iterTransitions = null;
        TransitionInput tInput = null;

        try {

            issue = getPlainJiraIssue(issueId);
            promise = getJiraClient().getIssueClient().getTransitions(issue);
            iterTransitions = promise.claim().iterator();

            if (isTransitionAllowed(iterTransitions, transitionId)) {

                tInput = new TransitionInput(transitionId);
                getJiraClient().getIssueClient().transition(issue, tInput);
                LOG.debug("Transition performed for issue " + issue.getKey() + " with transition id " + transitionId);

            } else
                throw new JiraClientException("Transition with id " + transitionId + " is not allowed for issue "
                        + issue.getKey());

        } catch (Exception e) {

            throw new JiraClientException("Was not able to transition Jira issue " + issueId + "!", e);
        }
    }

    /**
     * Check if the given transition id is an allowed transition regarding the given transitions.
     * 
     * @param iterTransitions All possible transitions.
     * @param transitionId The transition to be performed.
     * @return true, if the id describes an allowed transition.
     */
    private boolean isTransitionAllowed(Iterator<Transition> iterTransitions, int transitionId) {

        boolean isAllowed = false;

        while (iterTransitions.hasNext()) {

            if (iterTransitions.next().getId() == transitionId) {

                isAllowed = true;
                break;
            }
        }

        return isAllowed;
    }

Hope those starter-examples will be useful. Feel free to leave a comment.

, ,

No comments yet.

Leave a Reply

* Copy This Password *

* Type Or Paste Password Here *