Invariant Properties

  • rss
  • Home

Using the JIRA REST java client: Issues

Bear Giles | August 12, 2013

In the last article I discussed the JIRA projects – how to get information about a project. In this article I’ll discuss how to create simple JIRA issues and search for them.

An example motivation for programmatically-defining JIRA issues was discussed in the introduction. Imagine you want to keep track of unhandled exceptions that reach the top of the stack on your web application. It’s easy to lose track of them if they aren’t fully integrated into your work process – creating a JIRA Bug issue and a single-point story to investigate the bug would be a really good way to keep them in view. This article will give you a good start on this integration.

(If you do write an exception->tool remember to compute a fingerprint of some sort, e.g., an MD5 hash of the stack trace, and perform a search for that before creating a new JIRA entry. Otherwise you run the risk of finding hundreds or thousands of new JIRA entries after a bad day. Per-incident details can be added as an attachment.)

Creating a Minimal Issue

The only required fields for a JIRA issue are the project key, the issue type, and a summary. There are five standard issue types (Bug, Task, Subtask, Improvement, New Features) with well-known IDs. Plugins and/or administrators can add an arbitrary number of additional issue types, for example “applications”, “builds” and “defects”.

  1. package com.invariantproperties.sandbox.jira.example;
  2.  
  3. import static org.junit.Assert.assertEquals;
  4.  
  5. import java.io.IOException;
  6. import java.net.URI;
  7. import java.util.ResourceBundle;
  8.  
  9. import org.junit.Test;
  10.  
  11. import com.atlassian.jira.rest.client.api.IssueRestClient;
  12. import com.atlassian.jira.rest.client.api.JiraRestClient;
  13. import com.atlassian.jira.rest.client.api.domain.BasicIssue;
  14. import com.atlassian.jira.rest.client.api.domain.Issue;
  15. import com.atlassian.jira.rest.client.api.domain.IssueType;
  16. import com.atlassian.jira.rest.client.api.domain.input.IssueInput;
  17. import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder;
  18. import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
  19.  
  20. /**
  21.  * Simple tests of Issue functionality.
  22.  *
  23.  * @author Bear Giles
  24.  */
  25. public class IssueTest {
  26.     private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
  27.     private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
  28.     private static final String username = bundle.getString("username");
  29.     private static final String password = bundle.getString("password");
  30.  
  31.     private static final String PROJECT_KEY = "TST";
  32.     private static final long BUG_TYPE_ID = 1L; // JIRA magic value
  33.  
  34.     /**
  35.      * Create an minimal issue, verify that we can retrieve it.
  36.      */
  37.     @Test
  38.     public void createMinimalIssue() throws IOException {
  39.         final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
  40.                 serverUri, username, password);
  41.  
  42.         final IssueInputBuilder builder = new IssueInputBuilder(PROJECT_KEY, BUG_TYPE_ID, SUMMARY);
  43.         final IssueInput input = builder.build();
  44.  
  45.         try {
  46.             // create issue
  47.             final IssueRestClient client = restClient.getIssueClient();
  48.             final BasicIssue issue = client.createIssue(input).claim();
  49.  
  50.             assertNotNull(issue.getKey());
  51.  
  52.             // retrieve issue
  53.             final Issue actual = client.getIssue(issue.getKey()).claim();
  54.  
  55.             assertEquals(PROJECT_KEY, actual.getProject().getKey());
  56.             assertEquals(BUG_TYPE_ID, actual.getIssueType().getId().longValue());
  57.             assertEquals(SUMMARY, actual.getSummary());
  58.  
  59.             // post 2.0.0-m25 we can delete issue to reduce clutter
  60.             client.deleteIssue(issue.getKey(), false).claim();
  61.         } finally {
  62.             if (restClient != null) {
  63.                 restClient.close();
  64.             }
  65.         }
  66.     }
  67. }
package com.invariantproperties.sandbox.jira.example;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.net.URI;
import java.util.ResourceBundle;

import org.junit.Test;

import com.atlassian.jira.rest.client.api.IssueRestClient;
import com.atlassian.jira.rest.client.api.JiraRestClient;
import com.atlassian.jira.rest.client.api.domain.BasicIssue;
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.atlassian.jira.rest.client.api.domain.IssueType;
import com.atlassian.jira.rest.client.api.domain.input.IssueInput;
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder;
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;

/**
 * Simple tests of Issue functionality.
 * 
 * @author Bear Giles 
 */
public class IssueTest {
    private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
    private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
    private static final String username = bundle.getString("username");
    private static final String password = bundle.getString("password");

    private static final String PROJECT_KEY = "TST";
    private static final long BUG_TYPE_ID = 1L; // JIRA magic value

    /**
     * Create an minimal issue, verify that we can retrieve it.
     */
    @Test
    public void createMinimalIssue() throws IOException {
        final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
                serverUri, username, password);

        final IssueInputBuilder builder = new IssueInputBuilder(PROJECT_KEY, BUG_TYPE_ID, SUMMARY);
        final IssueInput input = builder.build();

        try {
            // create issue
            final IssueRestClient client = restClient.getIssueClient();
            final BasicIssue issue = client.createIssue(input).claim();

            assertNotNull(issue.getKey());

            // retrieve issue
            final Issue actual = client.getIssue(issue.getKey()).claim();

            assertEquals(PROJECT_KEY, actual.getProject().getKey());
            assertEquals(BUG_TYPE_ID, actual.getIssueType().getId().longValue());
            assertEquals(SUMMARY, actual.getSummary());

            // post 2.0.0-m25 we can delete issue to reduce clutter
            client.deleteIssue(issue.getKey(), false).claim();
        } finally {
            if (restClient != null) {
                restClient.close();
            }
        }
    }
}

Subtasks

Issues can be related in two ways. First, they can be related in a 1:N relationship between parent and children. The children’s issue type must have the subtask flag set. Of the original five issue types only the Subtask is a subtask.

The children do not all have to be the same type. For instance a static analysis issue type might have an application (parent), its builds (one type of children), and its defects (another type of children).

Issues can also be related on a peer basis. E.g., one bug might duplicate another bug, or a new feature might supercede another one. That is a topic for another day.

It is not difficult to create a parent-child relationship, but it is definitely not something that’s immediately obvious from reading either the JRJC or REST API.

  1. public class IssueTest {
  2.     private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
  3.     private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
  4.     private static final String username = bundle.getString("username");
  5.     private static final String password = bundle.getString("password");
  6.  
  7.     private static final String PROJECT_KEY = "TST";
  8.     private static final long TASK_TYPE_ID = 3L; // JIRA magic value
  9.     private static final long SUBTASK_TYPE_ID = 5L; // JIRA magic value
  10.     private static final String SUMMARY = "summary";
  11.  
  12.     /**
  13.      * Create an minimal issue and subtask, verify that we can retrieve them.
  14.      */
  15.     @Test
  16.     public void createMinimalIssueAndSubtask() throws IOException, JSONException {
  17.         final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
  18.                 serverUri, username, password);
  19.  
  20.         final IssueInputBuilder parentBuilder = new IssueInputBuilder(PROJECT_KEY, TASK_TYPE_ID, "parent");
  21.         final IssueInput parentInput = parentBuilder.build();
  22.  
  23.         try {
  24.             // create task
  25.             final IssueRestClient client = restClient.getIssueClient();
  26.             final BasicIssue parent = client.createIssue(parentInput).claim();
  27.  
  28.             assertNotNull(parent.getKey());
  29.             System.out.println("parent: " + parent);
  30.  
  31.             // create subtask
  32.             final IssueInputBuilder childBuilder = new IssueInputBuilder(PROJECT_KEY, SUBTASK_TYPE_ID, "child");
  33.             childBuilder.setFieldValue("parent", ComplexIssueInputFieldValue.with("key", parent.getKey()));
  34.             final BasicIssue child = client.createIssue(childBuilder.build()).claim();
  35.  
  36.             assertNotNull(child.getKey());
  37.  
  38.             // retrieve parent
  39.             final Issue actual = client.getIssue(parent.getKey()).claim();
  40.  
  41.             assertEquals(PROJECT_KEY, actual.getProject().getKey());
  42.             assertEquals("parent", actual.getSummary());
  43.  
  44.             // retrieve children, verify 'parent' points back to parent.
  45.             for (final Subtask subtask : actual.getSubtasks()) {
  46.                 final Issue actualChild = client.getIssue(subtask.getIssueKey()).claim();
  47.                 final JSONObject json = (JSONObject) actualChild.getField("parent").getValue();
  48.                 assertEquals(parent.getKey(), json.getString("key"));
  49.             }
  50.  
  51.             // post 2.0.0-m25 we can delete issue to reduce clutter
  52.             client.deleteIssue(parent.getKey(), true).claim();
  53.         } finally {
  54.             if (restClient != null) {
  55.                 restClient.close();
  56.             }
  57.         }
  58.     }
  59. }
public class IssueTest {
    private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
    private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
    private static final String username = bundle.getString("username");
    private static final String password = bundle.getString("password");

    private static final String PROJECT_KEY = "TST";
    private static final long TASK_TYPE_ID = 3L; // JIRA magic value
    private static final long SUBTASK_TYPE_ID = 5L; // JIRA magic value
    private static final String SUMMARY = "summary";

    /**
     * Create an minimal issue and subtask, verify that we can retrieve them.
     */
    @Test
    public void createMinimalIssueAndSubtask() throws IOException, JSONException {
        final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
                serverUri, username, password);

        final IssueInputBuilder parentBuilder = new IssueInputBuilder(PROJECT_KEY, TASK_TYPE_ID, "parent");
        final IssueInput parentInput = parentBuilder.build();

        try {
            // create task
            final IssueRestClient client = restClient.getIssueClient();
            final BasicIssue parent = client.createIssue(parentInput).claim();

            assertNotNull(parent.getKey());
            System.out.println("parent: " + parent);

            // create subtask
            final IssueInputBuilder childBuilder = new IssueInputBuilder(PROJECT_KEY, SUBTASK_TYPE_ID, "child");
            childBuilder.setFieldValue("parent", ComplexIssueInputFieldValue.with("key", parent.getKey()));
            final BasicIssue child = client.createIssue(childBuilder.build()).claim();

            assertNotNull(child.getKey());

            // retrieve parent
            final Issue actual = client.getIssue(parent.getKey()).claim();

            assertEquals(PROJECT_KEY, actual.getProject().getKey());
            assertEquals("parent", actual.getSummary());

            // retrieve children, verify 'parent' points back to parent.
            for (final Subtask subtask : actual.getSubtasks()) {
                final Issue actualChild = client.getIssue(subtask.getIssueKey()).claim();
                final JSONObject json = (JSONObject) actualChild.getField("parent").getValue();
                assertEquals(parent.getKey(), json.getString("key"));
            }

            // post 2.0.0-m25 we can delete issue to reduce clutter
            client.deleteIssue(parent.getKey(), true).claim();
        } finally {
            if (restClient != null) {
                restClient.close();
            }
        }
    }
}

Searching

There is no direct way to list all JIRA issues for a given project – you must perform a search. This can be done in on ad hoc basis using JQL queries, or done using canned queries. The easiest way to determine the correct JQL query is to bring up the advanced search page on the site and experiment.

The search results contain Issues, not just BasicIssues, but the objects are not fully populated and you may need to explictly reload the object. It is important to keep this in mind – I’ve been burned by this in the past.

  1. public class IssueTest {
  2.     private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
  3.     private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
  4.     private static final String username = bundle.getString("username");
  5.     private static final String password = bundle.getString("password");
  6.  
  7.     private static final String PROJECT_KEY = "TST";
  8.     private static final long BUG_TYPE_ID = 1L; // JIRA magic value
  9.  
  10.     /**
  11.      * Create an minimal issue, verify that we can search for it.
  12.      */
  13.     @Test
  14.     public void createMinimalIssueAndSearch() throws IOException {
  15.         final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
  16.                 serverUri, username, password);
  17.  
  18.         final IssueInputBuilder builder = new IssueInputBuilder(PROJECT_KEY, BUG_TYPE_ID, SUMMARY);
  19.         final IssueInput input = builder.build();
  20.  
  21.         try {
  22.             // create issue
  23.             final IssueRestClient issueClient = restClient.getIssueClient();
  24.             final BasicIssue issue = issueClient.createIssue(input).claim();
  25.  
  26.             assertNotNull(issue.getKey());
  27.  
  28.             final SearchRestClient searchClient = restClient.getSearchClient();
  29.             final String jql = "issuekey = \"" + issue.getKey() + "\"";
  30.             final SearchResult results = searchClient.searchJql(jql).claim();
  31.             assertEquals(1, results.getTotal());
  32.  
  33.             // retrieve results. We know there's only one.
  34.             for (final Issue result : results.getIssues()) {
  35.                 final Issue actual = issueClient.getIssue(result.getKey()).claim();
  36.  
  37.                 assertEquals(PROJECT_KEY, actual.getProject().getKey());
  38.                 assertEquals(BUG_TYPE_ID, actual.getIssueType().getId().longValue());
  39.                 assertEquals(SUMMARY, actual.getSummary());
  40.             }
  41.  
  42.             // post 2.0.0-m25 we can delete issue to reduce clutter
  43.             issueClient.deleteIssue(issue.getKey(), false).claim();
  44.         } finally {
  45.             if (restClient != null) {
  46.                 restClient.close();
  47.             }
  48.         }
  49.     }
  50. }
public class IssueTest {
    private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
    private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
    private static final String username = bundle.getString("username");
    private static final String password = bundle.getString("password");

    private static final String PROJECT_KEY = "TST";
    private static final long BUG_TYPE_ID = 1L; // JIRA magic value

    /**
     * Create an minimal issue, verify that we can search for it.
     */
    @Test
    public void createMinimalIssueAndSearch() throws IOException {
        final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
                serverUri, username, password);

        final IssueInputBuilder builder = new IssueInputBuilder(PROJECT_KEY, BUG_TYPE_ID, SUMMARY);
        final IssueInput input = builder.build();

        try {
            // create issue
            final IssueRestClient issueClient = restClient.getIssueClient();
            final BasicIssue issue = issueClient.createIssue(input).claim();

            assertNotNull(issue.getKey());

            final SearchRestClient searchClient = restClient.getSearchClient();
            final String jql = "issuekey = \"" + issue.getKey() + "\"";
            final SearchResult results = searchClient.searchJql(jql).claim();
            assertEquals(1, results.getTotal());

            // retrieve results. We know there's only one.
            for (final Issue result : results.getIssues()) {
                final Issue actual = issueClient.getIssue(result.getKey()).claim();

                assertEquals(PROJECT_KEY, actual.getProject().getKey());
                assertEquals(BUG_TYPE_ID, actual.getIssueType().getId().longValue());
                assertEquals(SUMMARY, actual.getSummary());
            }

            // post 2.0.0-m25 we can delete issue to reduce clutter
            issueClient.deleteIssue(issue.getKey(), false).claim();
        } finally {
            if (restClient != null) {
                restClient.close();
            }
        }
    }
}

Edited to add: I’m doing a search by issue key for illustration only – it’s the only issue that I know exists and is unique. In practice you would always load an issue directly if you already know the key.

Setting Additional Fields

It is easy to set standard additional fields when creating JIRA issues. Strings and dates are easy, components and versions are only slightly more difficult but I’m putting off that discussion until later.

User-defined custom fields are one of the more powerful features of JIRA. For now I’ll take the creation of custom fields via the website as a given. It is technically straightforward to set additional fields when creating issues but it is a more difficult to know the correct field id, e.g., “customfield_10317”. I’ll come back to that below.

N.B., it is important to remember that issue types may only allow some fields to be set when an issue is created. This is a common source of errors. (See JIRA “screens”)

  1. public class IssueTest {
  2.     private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
  3.     private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
  4.     private static final String username = bundle.getString("username");
  5.     private static final String password = bundle.getString("password");
  6.  
  7.     private static final String PROJECT_KEY = "TST";
  8.     private static final long BUG_TYPE_ID = 1L; // JIRA magic value
  9.     private static final String PRIORITY = "Trivial";
  10.     private static final String DESCRIPTION = "description";
  11.     private static final DateTime DUE_DATE = new DateTime();
  12.     private static final String COLOR = "Red";
  13.  
  14.     /**
  15.      * Create a simple issue, verify that we can retrieve it. This test requires
  16.      * that a custom select field ("Color", key "customfield_10000") with
  17.      * possible values of "Red", "Green" and "Blue" be created via the standard
  18.      * web interface. It also requires the custom multi-select field
  19.      * ("Toner Colors", key "customfield_10100") with possible values of
  20.      * "Yellow", "Magenta", "Cyan", and "Black".
  21.      *
  22.      * Implementation note: there are far more Issue getters than Builder
  23.      * setters. This can be a bit confusing if you don't have a list of fields.
  24.      */
  25.     @Test
  26.     public void createSimpleIssue() throws IOException, JSONException {
  27.         final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
  28.                 serverUri, username, password);
  29.  
  30.         final IssueInputBuilder builder = new IssueInputBuilder(PROJECT_KEY, BUG_TYPE_ID, SUMMARY);
  31.         builder.setDescription(DESCRIPTION);
  32.         builder.setDueDate(DUE_DATE);
  33.         builder.setFieldValue("priority", ComplexIssueInputFieldValue.with("name", PRIORITY));
  34.         // set single-select value
  35.         builder.setFieldValue("customfield_10000", ComplexIssueInputFieldValue.with("value", COLOR));
  36.         // set multi-select value
  37.         builder.setFieldValue(
  38.                 "customfield_10100",
  39.                 Arrays.asList(ComplexIssueInputFieldValue.with("value", "Yellow"),
  40.                         ComplexIssueInputFieldValue.with("value", "Black")));
  41.         final IssueInput input = builder.build();
  42.  
  43.         try {
  44.             // create issue
  45.             final IssueRestClient client = restClient.getIssueClient();
  46.             final BasicIssue issue = client.createIssue(input).claim();
  47.  
  48.             assertNotNull(issue.getKey());
  49.  
  50.             // retrieve issue
  51.             final Issue actual = client.getIssue(issue.getKey()).claim();
  52.  
  53.             assertEquals(PROJECT_KEY, actual.getProject().getKey());
  54.             assertEquals(BUG_TYPE_ID, actual.getIssueType().getId().longValue());
  55.             assertEquals(SUMMARY, actual.getSummary());
  56.             assertEquals(PRIORITY, actual.getPriority().getName());
  57.             assertEquals(DESCRIPTION, actual.getDescription());
  58.  
  59.             // we can't check date because of timezone weirdness.
  60.             // assertEquals(DUE_DATE, actual.getDueDate());
  61.  
  62.             // the 'value' of a custom field is actually a JSON object with the
  63.             // fields (self, value, id).
  64.             final JSONObject json = (JSONObject) actual.getField("customfield_10000").getValue();
  65.             assertEquals(COLOR, json.getString("value"));
  66.  
  67.             // the value of a multiselect is a JSONArray of JSON objects.
  68.             final JSONArray jarray = (JSONArray) actual.getField("customfield_10100").getValue();
  69.             assertEquals("Yellow", ((JSONObject) jarray.get(0)).getString("value"));
  70.             assertEquals("Black", ((JSONObject) jarray.get(1)).getString("value"));
  71.  
  72.             // we will be the 'reporter'
  73.             final User user = restClient.getUserClient().getUser(bundle.getString("username")).claim();
  74.             assertEquals(user, actual.getReporter());
  75.  
  76.             // in production the 'reporter' will probably not be the default
  77.             // 'assignee' but it's another field we can check.
  78.             assertEquals(user, actual.getAssignee());
  79.  
  80.             // post 2.0.0-m25 we can delete issue to reduce clutter
  81.             client.deleteIssue(issue.getKey(), false).claim();
  82.         } finally {
  83.             if (restClient != null) {
  84.                 restClient.close();
  85.             }
  86.         }
  87.     }
  88. }
public class IssueTest {
    private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
    private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
    private static final String username = bundle.getString("username");
    private static final String password = bundle.getString("password");

    private static final String PROJECT_KEY = "TST";
    private static final long BUG_TYPE_ID = 1L; // JIRA magic value
    private static final String PRIORITY = "Trivial";
    private static final String DESCRIPTION = "description";
    private static final DateTime DUE_DATE = new DateTime();
    private static final String COLOR = "Red";

    /**
     * Create a simple issue, verify that we can retrieve it. This test requires
     * that a custom select field ("Color", key "customfield_10000") with
     * possible values of "Red", "Green" and "Blue" be created via the standard
     * web interface. It also requires the custom multi-select field
     * ("Toner Colors", key "customfield_10100") with possible values of
     * "Yellow", "Magenta", "Cyan", and "Black".
     * 
     * Implementation note: there are far more Issue getters than Builder
     * setters. This can be a bit confusing if you don't have a list of fields.
     */
    @Test
    public void createSimpleIssue() throws IOException, JSONException {
        final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
                serverUri, username, password);

        final IssueInputBuilder builder = new IssueInputBuilder(PROJECT_KEY, BUG_TYPE_ID, SUMMARY);
        builder.setDescription(DESCRIPTION);
        builder.setDueDate(DUE_DATE);
        builder.setFieldValue("priority", ComplexIssueInputFieldValue.with("name", PRIORITY));
        // set single-select value
        builder.setFieldValue("customfield_10000", ComplexIssueInputFieldValue.with("value", COLOR));
        // set multi-select value
        builder.setFieldValue(
                "customfield_10100",
                Arrays.asList(ComplexIssueInputFieldValue.with("value", "Yellow"),
                        ComplexIssueInputFieldValue.with("value", "Black")));
        final IssueInput input = builder.build();

        try {
            // create issue
            final IssueRestClient client = restClient.getIssueClient();
            final BasicIssue issue = client.createIssue(input).claim();

            assertNotNull(issue.getKey());

            // retrieve issue
            final Issue actual = client.getIssue(issue.getKey()).claim();

            assertEquals(PROJECT_KEY, actual.getProject().getKey());
            assertEquals(BUG_TYPE_ID, actual.getIssueType().getId().longValue());
            assertEquals(SUMMARY, actual.getSummary());
            assertEquals(PRIORITY, actual.getPriority().getName());
            assertEquals(DESCRIPTION, actual.getDescription());

            // we can't check date because of timezone weirdness.
            // assertEquals(DUE_DATE, actual.getDueDate());

            // the 'value' of a custom field is actually a JSON object with the
            // fields (self, value, id).
            final JSONObject json = (JSONObject) actual.getField("customfield_10000").getValue();
            assertEquals(COLOR, json.getString("value"));

            // the value of a multiselect is a JSONArray of JSON objects.
            final JSONArray jarray = (JSONArray) actual.getField("customfield_10100").getValue();
            assertEquals("Yellow", ((JSONObject) jarray.get(0)).getString("value"));
            assertEquals("Black", ((JSONObject) jarray.get(1)).getString("value"));

            // we will be the 'reporter'
            final User user = restClient.getUserClient().getUser(bundle.getString("username")).claim();
            assertEquals(user, actual.getReporter());

            // in production the 'reporter' will probably not be the default
            // 'assignee' but it's another field we can check.
            assertEquals(user, actual.getAssignee());

            // post 2.0.0-m25 we can delete issue to reduce clutter
            client.deleteIssue(issue.getKey(), false).claim();
        } finally {
            if (restClient != null) {
                restClient.close();
            }
        }
    }
}

Editing Issues

It is not possible to edit issues via the JRJC library. It can be done via the REST API. The REST API uses a change log approach, versus simply calling PUT with a JSON representation of the modified Issue object.

Identifying Custom Fields

We finally reach three questions regarding custom fields.

The first question is “what is a list of ALL fields known to the system?” We can answer this via both the REST API and the JRJC library. See below.

The second question is “what is a list of fields that I can set when creating (or editing) an issue?”. We can answer this via the REST API but not (as I write this) the JRJC library. In many ways this is a more interesting question.

(The REST API call is /rest/api/2/issue/createmeta with optional parameters projectIds=, projectKeys=, issuetypeIds=, or issuetypeNames=. If you want to specify multiple projectIds etc. you can either repeat a parameter or use a comma-separated list.)

The final question is “what are the possible values of this custom field?” I touched on this earlier when I mentioned the custom field “Color” with possible values of “Blue”, “Green”, and “Red”. Again we can answer this via the REST API but not (as I write this) the JRJC library.

(The REST API is either the one mentioned above or /rest/api/2/field, with the additional parameter expand=projects.issuetypes.fields.)

The following code prints a list of all JIRA and CUSTOM fields.

  1. public class IssueTest {
  2.     private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
  3.     private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
  4.     private static final String username = bundle.getString("username");
  5.     private static final String password = bundle.getString("password");
  6.  
  7.     /**
  8.      * Query for creation metadata - this tells is the possible values for issue
  9.      * type, priority, and resolution. It also tells us the name and type of
  10.      * standard and custom fields.
  11.      */
  12.     // @Test
  13.     public void learnCreateMetadata() throws IOException {
  14.         final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
  15.                 serverUri, username, password);
  16.  
  17.         try {
  18.             final MetadataRestClient client = restClient.getMetadataClient();
  19.  
  20.             // list issue types
  21.             System.out.println("issue types:");
  22.             for (IssueType type : client.getIssueTypes().claim()) {
  23.                 System.out.printf("- %s (%d): %s\n", type.getName(), type.getId(), type.getDescription());
  24.             }
  25.             System.out.println();
  26.  
  27.             System.out.println("priorities");
  28.             for (Priority priority : client.getPriorities().claim()) {
  29.                 System.out.printf("- %s (%d): %s\n", priority.getName(), priority.getId(), priority.getDescription());
  30.             }
  31.             System.out.println();
  32.  
  33.             System.out.println("resolutions");
  34.             for (Resolution resolution : client.getResolutions().claim()) {
  35.                 System.out.printf("- %s: %s\n", resolution.getName(), resolution.getDescription());
  36.             }
  37.             System.out.println();
  38.  
  39.             System.out.println("jira fields");
  40.             for (Field field : client.getFields().claim()) {
  41.                 if (FieldType.JIRA.equals(field.getFieldType())) {
  42.                     System.out.printf("- %s (%s) %s\n", field.getName(), field.getId(),
  43.                             (field.getSchema() != null) ? field.getSchema().getType() : "(not specified)");
  44.                 }
  45.             }
  46.             System.out.println();
  47.  
  48.             // we can get possible values with
  49.             // http://localhost:8080/rest/api/2/issue/createmeta?expand=projects.issuetypes.fields
  50.             // but there's no JRJC for that (yet).
  51.             System.out.println("custom fields");
  52.             for (Field field : client.getFields().claim()) {
  53.                 if (FieldType.CUSTOM.equals(field.getFieldType())) {
  54.                     System.out.printf("- %s (%s) %s\n", field.getName(), field.getId(),
  55.                             (field.getSchema() != null) ? field.getSchema().getType() : "(not specified)");
  56.                 }
  57.             }
  58.         } finally {
  59.             if (restClient != null) {
  60.                 restClient.close();
  61.             }
  62.         }
  63.     }
  64. }
public class IssueTest {
    private static final ResourceBundle bundle = ResourceBundle.getBundle(IssueTest.class.getName());
    private static final URI serverUri = URI.create(bundle.getString("jiraUrl"));
    private static final String username = bundle.getString("username");
    private static final String password = bundle.getString("password");

    /**
     * Query for creation metadata - this tells is the possible values for issue
     * type, priority, and resolution. It also tells us the name and type of
     * standard and custom fields.
     */
    // @Test
    public void learnCreateMetadata() throws IOException {
        final JiraRestClient restClient = new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
                serverUri, username, password);

        try {
            final MetadataRestClient client = restClient.getMetadataClient();

            // list issue types
            System.out.println("issue types:");
            for (IssueType type : client.getIssueTypes().claim()) {
                System.out.printf("- %s (%d): %s\n", type.getName(), type.getId(), type.getDescription());
            }
            System.out.println();

            System.out.println("priorities");
            for (Priority priority : client.getPriorities().claim()) {
                System.out.printf("- %s (%d): %s\n", priority.getName(), priority.getId(), priority.getDescription());
            }
            System.out.println();

            System.out.println("resolutions");
            for (Resolution resolution : client.getResolutions().claim()) {
                System.out.printf("- %s: %s\n", resolution.getName(), resolution.getDescription());
            }
            System.out.println();

            System.out.println("jira fields");
            for (Field field : client.getFields().claim()) {
                if (FieldType.JIRA.equals(field.getFieldType())) {
                    System.out.printf("- %s (%s) %s\n", field.getName(), field.getId(),
                            (field.getSchema() != null) ? field.getSchema().getType() : "(not specified)");
                }
            }
            System.out.println();

            // we can get possible values with
            // http://localhost:8080/rest/api/2/issue/createmeta?expand=projects.issuetypes.fields
            // but there's no JRJC for that (yet).
            System.out.println("custom fields");
            for (Field field : client.getFields().claim()) {
                if (FieldType.CUSTOM.equals(field.getFieldType())) {
                    System.out.printf("- %s (%s) %s\n", field.getName(), field.getId(),
                            (field.getSchema() != null) ? field.getSchema().getType() : "(not specified)");
                }
            }
        } finally {
            if (restClient != null) {
                restClient.close();
            }
        }
    }
}

On my system the results are:

JIRA Fields

Name ID Type
Progress progress progress
Summary summary string
Time Tracking timetracking timetracking
Key issuekey (not specified)
Issue Type issuetype issuetype
Votes votes array
Security Level security securitylevel
Fix Version/s fixVersions array
Resolution resolution resolution
Resolved resolutiondate datetime
Time Spent timespent number
Reporter reporter user
Σ Original Estimate aggregatetimeoriginalestimate number
Updated updated datetime
Created created datetime
Description description string
Priority priority priority
Due Date duedate date
Linked Issues issuelinks array
Watchers watches array
Log Work worklog array
Sub-Tasks subtasks array
Status status status
Labels labels array
Work Ratio workratio number
Assignee assignee user
Attachment attachment array
Σ Remaining Estimate aggregatetimeestimate number
Affects Version/s versions array
Project project project
Environment environment string
Images thumbnail (not specified)
Remaining Estimate timeestimate number
Σ Progress aggregateprogress progress
Last Viewed lastViewed datetime
Component/s components array
Comment comment array
Original Estimate timeoriginalestimate number
Σ Time Spent aggregatetimespent number

Custom Fields

Name ID Type
Color customfield_10000 string
Categories
java
Comments rss
Comments rss
Trackback
Trackback

« Using the JIRA REST java client: Projects Estimating VO2max in Boulder »

Leave a Reply

Click here to cancel reply.

You must be logged in to post a comment.

Archives

  • May 2020 (1)
  • March 2019 (1)
  • August 2018 (1)
  • May 2018 (1)
  • February 2018 (1)
  • November 2017 (4)
  • January 2017 (3)
  • June 2016 (1)
  • May 2016 (1)
  • April 2016 (2)
  • March 2016 (1)
  • February 2016 (3)
  • January 2016 (6)
  • December 2015 (2)
  • November 2015 (3)
  • October 2015 (2)
  • August 2015 (4)
  • July 2015 (2)
  • June 2015 (2)
  • January 2015 (1)
  • December 2014 (6)
  • October 2014 (1)
  • September 2014 (2)
  • August 2014 (1)
  • July 2014 (1)
  • June 2014 (2)
  • May 2014 (2)
  • April 2014 (1)
  • March 2014 (1)
  • February 2014 (3)
  • January 2014 (6)
  • December 2013 (13)
  • November 2013 (6)
  • October 2013 (3)
  • September 2013 (2)
  • August 2013 (5)
  • June 2013 (1)
  • May 2013 (2)
  • March 2013 (1)
  • November 2012 (1)
  • October 2012 (3)
  • September 2012 (2)
  • May 2012 (6)
  • January 2012 (2)
  • December 2011 (12)
  • July 2011 (1)
  • June 2011 (2)
  • May 2011 (5)
  • April 2011 (6)
  • March 2011 (4)
  • February 2011 (3)
  • October 2010 (6)
  • September 2010 (8)

Recent Posts

  • 8-bit Breadboard Computer: Good Encapsulation!
  • Where are all the posts?
  • Better Ad Blocking Through Pi-Hole and Local Caching
  • The difference between APIs and SPIs
  • Hadoop: User Impersonation with Kerberos Authentication

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Pages

  • About Me
  • Notebook: Common XML Tasks
  • Notebook: Database/Webapp Security
  • Notebook: Development Tips

Syndication

Java Code Geeks

Know Your Rights

Support Bloggers' Rights
Demand Your dotRIGHTS

Security

  • Dark Reading
  • Krebs On Security Krebs On Security
  • Naked Security Naked Security
  • Schneier on Security Schneier on Security
  • TaoSecurity TaoSecurity

Politics

  • ACLU ACLU
  • EFF EFF

News

  • Ars technica Ars technica
  • Kevin Drum at Mother Jones Kevin Drum at Mother Jones
  • Raw Story Raw Story
  • Tech Dirt Tech Dirt
  • Vice Vice

Spam Blocked

53,793 spam blocked by Akismet
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox