diff --git a/AUTHORS.md b/AUTHORS.md index ff88050..bb04a26 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -3,4 +3,7 @@ Kyle Chaplin @chaplinkyle Alesandro Lang @alesandroLang Javier Molina @javinovich Joseph McCarthy +Magnus Lundberg +Anders Kreinøe @Kreinoee +Andrey Kuzmin @nach-o-man Pierre-Luc Dupont @pldupont diff --git a/README.md b/README.md index 9ff583b..fcbd586 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,11 @@ public class Example { /* Now let's start progress on this issue. */ issue.transition().execute("Start Progress"); + /* Add the first comment and update it */ + Comment comment = issue.addComment("I am a comment!"); + comment.update("I am the first comment!"); + issue.getComments().get(0).update("this works too!"); + /* Pretend customfield_1234 is a text field. Get the raw field value... */ Object cfvalue = issue.getField("customfield_1234"); @@ -194,6 +199,12 @@ public class Example { for (Issue i : sr.issues) System.out.println("Result: " + i); + /* Search with paging (optionally 10 issues at a time). There are optional + arguments for including/expanding fields, and page size/start. */ + Issue.SearchResult sr = jira.searchIssues("project IN (GOTHAM) ORDER BY id"); + while (sr.iterator().hasNext()) + System.out.println("Result: " + sr.iterator().next()); + } catch (JiraException ex) { System.err.println(ex.getMessage()); diff --git a/src/main/java/net/rcarz/jiraclient/Comment.java b/src/main/java/net/rcarz/jiraclient/Comment.java index 7d92824..ef53a73 100644 --- a/src/main/java/net/rcarz/jiraclient/Comment.java +++ b/src/main/java/net/rcarz/jiraclient/Comment.java @@ -30,6 +30,7 @@ import java.util.Map; */ public class Comment extends Resource { + private String issueKey = null; private User author = null; private String body = null; private Date created = null; @@ -42,9 +43,10 @@ public class Comment extends Resource { * @param restclient REST client instance * @param json JSON payload */ - protected Comment(RestClient restclient, JSONObject json) { + protected Comment(RestClient restclient, JSONObject json, String issueKey) { super(restclient); + this.issueKey = issueKey; if (json != null) deserialise(json); } @@ -86,7 +88,59 @@ public class Comment extends Resource { if (!(result instanceof JSONObject)) throw new JiraException("JSON payload is malformed"); - return new Comment(restclient, (JSONObject)result); + return new Comment(restclient, (JSONObject)result, issue); + } + + /** + * Updates the comment body. + * + * @param issue associated issue record + * @param body Comment text + * + * @throws JiraException when the comment update fails + */ + public void update(String body) throws JiraException { + update(body, null, null); + } + + /** + * Updates the comment body with limited visibility. + * + * @param issue associated issue record + * @param body Comment text + * @param visType Target audience type (role or group) + * @param visName Name of the role or group to limit visibility to + * + * @throws JiraException when the comment update fails + */ + public void update(String body, String visType, String visName) + throws JiraException { + + JSONObject req = new JSONObject(); + req.put("body", body); + + if (visType != null && visName != null) { + JSONObject vis = new JSONObject(); + vis.put("type", visType); + vis.put("value", visName); + + req.put("visibility", vis); + } + + JSON result = null; + + try { + String issueUri = getBaseUri() + "issue/" + issueKey; + result = restclient.put(issueUri + "/comment/" + id, req); + } catch (Exception ex) { + throw new JiraException("Failed add update comment " + id, ex); + } + + if (!(result instanceof JSONObject)) { + throw new JiraException("JSON payload is malformed"); + } + + deserialise((JSONObject) result); } @Override diff --git a/src/main/java/net/rcarz/jiraclient/Field.java b/src/main/java/net/rcarz/jiraclient/Field.java index f38fb86..0101914 100644 --- a/src/main/java/net/rcarz/jiraclient/Field.java +++ b/src/main/java/net/rcarz/jiraclient/Field.java @@ -19,24 +19,107 @@ package net.rcarz.jiraclient; -import net.sf.json.JSONArray; -import net.sf.json.JSONNull; -import net.sf.json.JSONObject; - +import java.lang.Iterable; +import java.lang.UnsupportedOperationException; import java.sql.Timestamp; import java.text.ParsePosition; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import net.sf.json.JSONNull; /** * Utility functions for translating between JSON and fields. */ public final class Field { + /** + * Field metadata structure. + */ + public static final class Meta { + public boolean required; + public String type; + public String items; + public String name; + public String system; + public String custom; + public int customId; + } + + /** + * Field update operation. + */ + public static final class Operation { + public String name; + public Object value; + + /** + * Initialises a new update operation. + * + * @param name Operation name + * @param value Field value + */ + public Operation(String name, Object value) { + this.name = name; + this.value = value; + } + } + + /** + * Allowed value types. + */ + public enum ValueType { + KEY("key"), NAME("name"), ID_NUMBER("id"), VALUE("value"); + private String typeName; + + private ValueType(String typeName) { + this.typeName = typeName; + } + + @Override + public String toString() { + return typeName; + } + }; + + /** + * Value and value type pair. + */ + public static final class ValueTuple { + public final String type; + public final Object value; + + /** + * Initialises the value tuple. + * + * @param type + * @param value + */ + public ValueTuple(String type, Object value) { + this.type = type; + this.value = (value != null ? value : JSONNull.getInstance()); + } + + /** + * Initialises the value tuple. + * + * @param type + * @param value + */ + public ValueTuple(ValueType type, Object value) { + this(type.toString(), value); + } + } + public static final String ASSIGNEE = "assignee"; public static final String ATTACHMENT = "attachment"; public static final String CHANGE_LOG = "changelog"; - ; public static final String CHANGE_LOG_ENTRIES = "histories"; public static final String CHANGE_LOG_ITEMS = "items"; public static final String COMMENT = "comment"; @@ -66,8 +149,10 @@ public final class Field { public static final String CREATED_DATE = "created"; public static final String UPDATED_DATE = "updated"; public static final String TRANSITION_TO_STATUS = "to"; + public static final String DATE_FORMAT = "yyyy-MM-dd"; public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + private Field() { } /** @@ -91,14 +176,22 @@ public final class Field { * * @param c a JSONObject instance * @param restclient REST client instance + * @param issueKey key of the parent issue * * @return a list of comments found in c */ - public static List getComments(Object c, RestClient restclient) { + public static List getComments(Object c, RestClient restclient, + String issueKey) { List results = new ArrayList(); - if (c instanceof JSONObject && !((JSONObject)c).isNullObject()) - results = getResourceArray(Comment.class, ((Map)c).get("comments"), restclient); + if (c instanceof JSONObject && !((JSONObject)c).isNullObject()) { + results = getResourceArray( + Comment.class, + ((Map)c).get("comments"), + restclient, + issueKey + ); + } return results; } @@ -119,7 +212,7 @@ public final class Field { return results; } - + /** * Gets a list of remote links from the given object. * @@ -206,20 +299,19 @@ public final class Field { } /** - * Gets a long from the given object. - * - * @param i a Long or an Integer instance - * - * @return a long primitive or 0 if i isn't a Long or an Integer instance - */ + + * Gets a long from the given object. + + * + + * @param i a Long or an Integer instance + + * + + * @return a long primitive or 0 if i isn't a Long or an Integer instance + + */ public static long getLong(Object i) { long result = 0; - - if (i instanceof Long) + if (i instanceof Long) { result = ((Long) i).longValue(); - if (i instanceof Integer) + } else if (i instanceof Integer) { result = ((Integer) i).intValue(); - + } return result; } @@ -261,6 +353,22 @@ public final class Field { public static T getResource( Class type, Object r, RestClient restclient) { + return getResource(type, r, restclient, null); + } + + /** + * Gets a JIRA resource from the given object. + * + * @param type Resource data type + * @param r a JSONObject instance + * @param restclient REST client instance + * @param parentId id/key of the parent resource + * + * @return a Resource instance or null if r isn't a JSONObject instance + */ + public static T getResource( + Class type, Object r, RestClient restclient, String parentId) { + T result = null; if (r instanceof JSONObject && !((JSONObject)r).isNullObject()) { @@ -273,7 +381,7 @@ public final class Field { else if (type == ChangeLogItem.class) result = (T)new ChangeLogItem(restclient, (JSONObject)r); else if (type == Comment.class) - result = (T)new Comment(restclient, (JSONObject)r); + result = (T)new Comment(restclient, (JSONObject)r, parentId); else if (type == Component.class) result = (T)new Component(restclient, (JSONObject)r); else if (type == CustomFieldOption.class) @@ -290,6 +398,8 @@ public final class Field { result = (T)new Priority(restclient, (JSONObject)r); else if (type == Project.class) result = (T)new Project(restclient, (JSONObject)r); + else if (type == ProjectCategory.class) + result = (T)new ProjectCategory(restclient, (JSONObject)r); else if (type == RemoteLink.class) result = (T)new RemoteLink(restclient, (JSONObject)r); else if (type == Resolution.class) @@ -361,11 +471,34 @@ public final class Field { public static List getResourceArray( Class type, Object ra, RestClient restclient) { + return getResourceArray(type, ra, restclient, null); + } + + /** + * Gets a list of JIRA resources from the given object. + * + * @param type Resource data type + * @param ra a JSONArray instance + * @param restclient REST client instance + * @param parentId id/key of the parent resource + * + * @return a list of Resources found in ra + */ + public static List getResourceArray( + Class type, Object ra, RestClient restclient, String parentId) { + List results = new ArrayList(); if (ra instanceof JSONArray) { for (Object v : (JSONArray)ra) { - T item = getResource(type, v, restclient); + T item = null; + + if (parentId != null) { + item = getResource(type, v, restclient, parentId); + } else { + item = getResource(type, v, restclient); + } + if (item != null) results.add(item); } @@ -483,10 +616,12 @@ public final class Field { itemMap.put(ValueType.NAME.toString(), realValue.toString()); realResult = itemMap; - } else if (type.equals("string") && custom != null + } else if ( type.equals("option") || + ( + type.equals("string") && custom != null && (custom.equals("com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes") || - custom.equals("com.atlassian.jira.plugin.system.customfieldtypes:multiselect"))) { - + custom.equals("com.atlassian.jira.plugin.system.customfieldtypes:multiselect")))) { + realResult = new JSONObject(); ((JSONObject)realResult).put(ValueType.VALUE.toString(), realValue.toString()); } else if (type.equals("string")) @@ -591,7 +726,7 @@ public final class Field { else if (value instanceof TimeTracking) return ((TimeTracking) value).toJsonObject(); } else if (m.type.equals("number")) { - if (!(value instanceof java.lang.Integer) && !(value instanceof java.lang.Double) && !(value + if(!(value instanceof java.lang.Integer) && !(value instanceof java.lang.Double) && !(value instanceof java.lang.Float) && !(value instanceof java.lang.Long) ) { throw new JiraException("Field '" + name + "' expects a Numeric value"); @@ -655,83 +790,5 @@ public final class Field { public static ValueTuple valueById(String id) { return new ValueTuple(ValueType.ID_NUMBER, id); } - - /** - * Allowed value types. - */ - public enum ValueType { - KEY("key"), NAME("name"), ID_NUMBER("id"), VALUE("value"); - private String typeName; - - private ValueType(String typeName) { - this.typeName = typeName; - } - - @Override - public String toString() { - return typeName; - } - } - - /** - * Field metadata structure. - */ - public static final class Meta { - public boolean required; - public String type; - public String items; - public String name; - public String system; - public String custom; - public int customId; - } - - /** - * Field update operation. - */ - public static final class Operation { - public String name; - public Object value; - - /** - * Initialises a new update operation. - * - * @param name Operation name - * @param value Field value - */ - public Operation(String name, Object value) { - this.name = name; - this.value = value; - } - } - - /** - * Value and value type pair. - */ - public static final class ValueTuple { - public final String type; - public final Object value; - - /** - * Initialises the value tuple. - * - * @param type - * @param value - */ - public ValueTuple(String type, Object value) { - this.type = type; - this.value = (value != null ? value : JSONNull.getInstance()); - } - - /** - * Initialises the value tuple. - * - * @param type - * @param value - */ - public ValueTuple(ValueType type, Object value) { - this(type.toString(), value); - } - } } diff --git a/src/main/java/net/rcarz/jiraclient/Filter.java b/src/main/java/net/rcarz/jiraclient/Filter.java new file mode 100644 index 0000000..f2756f9 --- /dev/null +++ b/src/main/java/net/rcarz/jiraclient/Filter.java @@ -0,0 +1,74 @@ +package net.rcarz.jiraclient; + +import net.sf.json.JSON; +import net.sf.json.JSONObject; + +import java.net.URI; +import java.util.Map; + +/** + * Represens a Jira filter. + */ +public class Filter extends Resource { + + private String name; + private String jql; + private boolean favourite; + + public Filter(RestClient restclient, JSONObject json) { + super(restclient); + + if (json != null) + deserialise(json); + } + + private void deserialise(JSONObject json) { + Map map = json; + + id = Field.getString(map.get("id")); + self = Field.getString(map.get("self")); + name = Field.getString(map.get("name")); + jql = Field.getString(map.get("jql")); + favourite = Field.getBoolean(map.get("favourite")); + } + + public boolean isFavourite() { + return favourite; + } + + public String getJql() { + return jql; + } + + public String getName() { + return name; + } + + public static Filter get(final RestClient restclient, final String id) throws JiraException { + JSON result = null; + + try { + URI uri = restclient.buildURI(getBaseUri() + "filter/" + id); + result = restclient.get(uri); + } catch (Exception ex) { + throw new JiraException("Failed to retrieve filter with id " + id, ex); + } + + if (!(result instanceof JSONObject)) { + throw new JiraException("JSON payload is malformed"); + } + + return new Filter(restclient, (JSONObject) result); + } + + @Override + public String toString() { + return "Filter{" + + "favourite=" + favourite + + ", name='" + name + '\'' + + ", jql='" + jql + '\'' + + '}'; + } + + +} diff --git a/src/main/java/net/rcarz/jiraclient/Issue.java b/src/main/java/net/rcarz/jiraclient/Issue.java index eafb6e8..1cb586a 100644 --- a/src/main/java/net/rcarz/jiraclient/Issue.java +++ b/src/main/java/net/rcarz/jiraclient/Issue.java @@ -1,7 +1,7 @@ /** * jira-client - a simple JIRA REST client * Copyright (c) 2013 Bob Carroll (bob.carroll@alum.rit.edu) - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -528,174 +528,181 @@ public class Issue extends Resource { } - /** + /** * Iterates over all issues in the query by getting the next page of * issues when the iterator reaches the last of the current page. */ private static class IssueIterator implements Iterator { - private Iterator currentPage; - private RestClient restclient; - private Issue nextIssue; + private Iterator currentPage; + private RestClient restclient; + private Issue nextIssue; private Integer maxResults = -1; - private String jql; - private String includedFields; - private String expandFields; - private Integer startAt; - private List issues; - private int total; - - public IssueIterator(RestClient restclient, String jql, - String includedFields, String expandFields, Integer maxResults, Integer startAt) - throws JiraException { - this.restclient = restclient; - this.jql = jql; - this.includedFields = includedFields; - this.expandFields = expandFields; - this.maxResults = maxResults; - this.startAt = startAt; + private String jql; + private String includedFields; + private String expandFields; + private Integer startAt; + private List issues; + private int total; + + public IssueIterator(RestClient restclient, String jql, String includedFields, + String expandFields, Integer maxResults, Integer startAt) + throws JiraException { + this.restclient = restclient; + this.jql = jql; + this.includedFields = includedFields; + this.expandFields = expandFields; + this.maxResults = maxResults; + this.startAt = startAt; + } + + @Override + public boolean hasNext() { + if (nextIssue != null) { + return true; + } + try { + nextIssue = getNextIssue(); + } catch (JiraException e) { + throw new RuntimeException(e); + } + return nextIssue != null; } - - @Override - public boolean hasNext() { - if (nextIssue != null) { - return true; - } - try { - nextIssue = getNextIssue(); - } catch (JiraException e) { - throw new RuntimeException(e); - } - return nextIssue != null; - } - @Override - public Issue next() { - if (! hasNext()) { - throw new NoSuchElementException(); - } - Issue result = nextIssue; - nextIssue = null; - return result; - } + @Override + public Issue next() { + if (! hasNext()) { + throw new NoSuchElementException(); + } + Issue result = nextIssue; + nextIssue = null; + return result; + } + @Override + public void remove() { + throw new UnsupportedOperationException("Method remove() not support for class " + + this.getClass().getName()); + } - @Override - public void remove() { - throw new UnsupportedOperationException("Method remove() not support for class " + this.getClass().getName()); - } + /** + * Gets the next issue, returning null if none more available + * Will ask the next set of issues from the server if the end + * of the current list of issues is reached. + * + * @return the next issue, null if none more available + * @throws JiraException + */ + private Issue getNextIssue() throws JiraException { + // first call + if (currentPage == null) { + currentPage = getNextIssues().iterator(); + if (currentPage == null || !currentPage.hasNext()) { + return null; + } else { + return currentPage.next(); + } + } + + // check if we need to get the next set of issues + if (! currentPage.hasNext()) { + currentPage = getNextIssues().iterator(); + } - /** - * Gets the next issue, returning null if none more available - * Will ask the next set of issues from the server if the end of the current list of issues is reached. - * - * @return the next issue, null if none more available - * @throws JiraException - */ - private Issue getNextIssue() throws JiraException { - // first call - if (currentPage == null) { - currentPage = getNextIssues().iterator(); - if (currentPage == null) { - return null; - } else { - return currentPage.next(); - } - } - - // check if we need to get the next set of issues - if (! currentPage.hasNext()) { - currentPage = getNextIssues().iterator(); - } + // return the next item if available + if (currentPage.hasNext()) { + return currentPage.next(); + } else { + return null; + } + } - // return the next item if available - if (currentPage.hasNext()) { - return currentPage.next(); - } else { - return null; - } - } + /** + * Execute the query to get the next set of issues. + * Also sets the startAt, maxMresults, total and issues fields, + * so that the SearchResult can access them. + * + * @return the next set of issues. + * @throws JiraException + */ + private List getNextIssues() throws JiraException { + if (issues == null && startAt == null) { + startAt = Integer.valueOf(0); + } else if (issues != null) { + startAt = startAt + issues.size(); + } - /** - * Execute the query to get the next set of issues. - * Also sets the startAt, maxMresults, total and issues fields, - * so that the SearchResult can access them. - * - * @return the next set of issues. - * @throws JiraException - */ - private List getNextIssues() throws JiraException { - if (issues == null) { - startAt = Integer.valueOf(0); - } else { - startAt = startAt + issues.size(); - } + JSON result = null; - JSON result = null; + try { + URI searchUri = createSearchURI(restclient, jql, includedFields, + expandFields, maxResults, startAt); + result = restclient.get(searchUri); + } catch (Exception ex) { + throw new JiraException("Failed to search issues", ex); + } - try { - URI searchUri = createSearchURI(restclient, jql, includedFields, - expandFields, maxResults, startAt); - result = restclient.get(searchUri); - } catch (Exception ex) { - throw new JiraException("Failed to search issues", ex); - } - - if (!(result instanceof JSONObject)) { - throw new JiraException("JSON payload is malformed"); - } - - - Map map = (Map) result; - - this.startAt = Field.getInteger(map.get("startAt")); - this.maxResults = Field.getInteger(map.get("maxResults")); - this.total = Field.getInteger(map.get("total")); - this.issues = Field.getResourceArray(Issue.class, map.get("issues"), restclient); - return issues; - } + if (!(result instanceof JSONObject)) { + throw new JiraException("JSON payload is malformed"); + } + + Map map = (Map) result; + + this.startAt = Field.getInteger(map.get("startAt")); + this.maxResults = Field.getInteger(map.get("maxResults")); + this.total = Field.getInteger(map.get("total")); + this.issues = Field.getResourceArray(Issue.class, map.get("issues"), restclient); + return issues; + } } /** * Issue search results structure. + * + * The issues of the result can be retrived from this class in 2 ways. + * + * The first is to access the issues field directly. This is a list of Issue instances. + * Note however that this will only contain the issues fetched in the initial search, + * so its size will be the same as the max result value or below. + * + * The second way is to use the iterator methods. This will return an Iterator instance, + * that will iterate over every result of the search, even if there is more than the max + * result value. The price for this, is that the call to next has none determistic performence, + * as it sometimes need to fetch a new batch of issues from Jira. */ public static class SearchResult { public int start = 0; public int max = 0; public int total = 0; public List issues = null; - private RestClient restclient; - private String jql; - private String includedFields; - private String expandFields; - private Integer startAt; - private IssueIterator issueIterator; + private IssueIterator issueIterator; - public SearchResult(RestClient restclient, String jql, - String includedFields, String expandFields, Integer maxResults, Integer startAt) throws JiraException { - this.restclient = restclient; - this.jql = jql; - this.includedFields = includedFields; - this.expandFields = expandFields; - initSearchResult(maxResults, start); + public SearchResult(RestClient restclient, String jql, String includedFields, + String expandFields, Integer maxResults, Integer startAt) + throws JiraException { + this.issueIterator = new IssueIterator( + restclient, + jql, + includedFields, + expandFields, + maxResults, + startAt + ); + /* backwards compatibility shim - first page only */ + this.issueIterator.hasNext(); + this.max = issueIterator.maxResults; + this.start = issueIterator.startAt; + this.issues = issueIterator.issues; + this.total = issueIterator.total; } - - private void initSearchResult(Integer maxResults, Integer start) throws JiraException { - this.issueIterator = new IssueIterator(restclient, jql, includedFields, expandFields, maxResults, startAt); - this.issueIterator.hasNext(); - this.max = issueIterator.maxResults; - this.start = issueIterator.startAt; - this.issues = issueIterator.issues; - this.total = issueIterator.total; - } - /** + /** * All issues found. * * @return All issues found. */ - public IssueIterator iterator() { - return issueIterator; + public Iterator iterator() { + return issueIterator; } } @@ -805,11 +812,13 @@ public class Issue extends Resource { key = Field.getString(map.get("key")); fields = (Map)map.get("fields"); + if (fields == null) + return; assignee = Field.getResource(User.class, fields.get(Field.ASSIGNEE), restclient); attachments = Field.getResourceArray(Attachment.class, fields.get(Field.ATTACHMENT), restclient); changeLog = Field.getResource(ChangeLog.class, map.get(Field.CHANGE_LOG), restclient); - comments = Field.getComments(fields.get(Field.COMMENT), restclient); + comments = Field.getComments(fields.get(Field.COMMENT), restclient, key); components = Field.getResourceArray(Component.class, fields.get(Field.COMPONENTS), restclient); description = Field.getString(fields.get(Field.DESCRIPTION)); dueDate = Field.getDate(fields.get(Field.DUE_DATE)); @@ -849,10 +858,10 @@ public class Issue extends Resource { JSON result = null; try { - Map params = new HashMap(); - params.put("expand", "projects.issuetypes.fields"); - params.put("projectKeys", pval); - params.put("issuetypeNames", itval); + Map params = new HashMap(); + params.put("expand", "projects.issuetypes.fields"); + params.put("projectKeys", pval); + params.put("issuetypeNames", itval); URI createuri = restclient.buildURI( getBaseUri() + "issue/createmeta", params); @@ -876,7 +885,7 @@ public class Issue extends Resource { restclient); if (projects.isEmpty() || projects.get(0).getIssueTypes().isEmpty()) - throw new JiraException("Project '"+ project + "' or issue type '" + issueType + + throw new JiraException("Project '"+ project + "' or issue type '" + issueType + "' missing from create metadata. Do you have enough permissions?"); return projects.get(0).getIssueTypes().get(0).getFields(); @@ -907,8 +916,8 @@ public class Issue extends Resource { JSON result = null; try { - Map params = new HashMap(); - params.put("expand", "transitions.fields"); + Map params = new HashMap(); + params.put("expand", "transitions.fields"); URI transuri = restclient.buildURI( getRestUri(key) + "/transitions",params); result = restclient.get(transuri); @@ -947,7 +956,7 @@ public class Issue extends Resource { throw new JiraException("Failed add attachment to issue " + key, ex); } } - + /** * Adds a remote link to this issue. * @@ -998,24 +1007,24 @@ public class Issue extends Resource { } /** - * Removes an attachments. - * - * @param attachmentId attachment id to remove - * - * @throws JiraException when the attachment removal fails - */ - public void removeAttachment(String attachmentId) throws JiraException { - - if (attachmentId == null) { - throw new NullPointerException("attachmentId may not be null"); - } - - try { - restclient.delete(getBaseUri() + "attachment/" + attachmentId); - } catch (Exception ex) { - throw new JiraException("Failed remove attachment " + attachmentId, ex); - } - } + * Removes an attachments. + * + * @param attachmentId attachment id to remove + * + * @throws JiraException when the attachment removal fails + */ + public void removeAttachment(String attachmentId) throws JiraException { + + if (attachmentId == null) { + throw new NullPointerException("attachmentId may not be null"); + } + + try { + restclient.delete(getBaseUri() + "attachment/" + attachmentId); + } catch (Exception ex) { + throw new JiraException("Failed remove attachment " + attachmentId, ex); + } + } /** * Adds a comment to this issue. @@ -1024,8 +1033,8 @@ public class Issue extends Resource { * * @throws JiraException when the comment creation fails */ - public void addComment(String body) throws JiraException { - addComment(body, null, null); + public Comment addComment(String body) throws JiraException { + return addComment(body, null, null); } /** @@ -1037,7 +1046,7 @@ public class Issue extends Resource { * * @throws JiraException when the comment creation fails */ - public void addComment(String body, String visType, String visName) + public Comment addComment(String body, String visType, String visName) throws JiraException { JSONObject req = new JSONObject(); @@ -1051,11 +1060,19 @@ public class Issue extends Resource { req.put("visibility", vis); } + JSON result = null; + try { - restclient.post(getRestUri(key) + "/comment", req); + result = restclient.post(getRestUri(key) + "/comment", req); } catch (Exception ex) { throw new JiraException("Failed add comment to issue " + key, ex); } + + if (!(result instanceof JSONObject)) { + throw new JiraException("JSON payload is malformed"); + } + + return new Comment(restclient, (JSONObject) result, key); } /** @@ -1248,7 +1265,7 @@ public class Issue extends Resource { *
  • summary,comment - include just the summary and comments
  • *
  • *all,-comment - include all fields
  • * - * + * * @param expand fields to expand when obtaining the issue * * @return an issue instance @@ -1260,56 +1277,12 @@ public class Issue extends Resource { Map queryParams = new HashMap(); queryParams.put("fields", includedFields); - if (expand != null) { - queryParams.put("expand", expand); - } + if (expand != null) { + queryParams.put("expand", expand); + } return new Issue(restclient, realGet(restclient, key, queryParams)); } - /** - * Search for issues with the given query. - * - * @param restclient REST client instance - * - * @param jql JQL statement - * - * @return a search result structure with results (issues include all - * navigable fields) - * - * @throws JiraException when the search fails - */ - public static SearchResult search(RestClient restclient, String jql) - throws JiraException { - return search(restclient, jql, null, null); - } - - /** - * Search for issues with the given query and specify which fields to - * retrieve. - * - * @param restclient REST client instance - * - * @param jql JQL statement - * - * @param includedFields Specifies which issue fields will be included in - * the result. - *
    Some examples how this parameter works: - *
      - *
    • *all - include all fields
    • - *
    • *navigable - include just navigable fields
    • - *
    • summary,comment - include just the summary and comments
    • - *
    • *all,-comment - include all fields
    • - *
    - * - * @return a search result structure with results - * - * @throws JiraException when the search fails - */ - public static SearchResult search(RestClient restclient, String jql, String includedFields, Integer maxResults) - throws JiraException { - return search(restclient, jql, includedFields, null, maxResults, null); - } - /** * Search for issues with the given query and specify which fields to * retrieve. If the total results is bigger than the maximum returned @@ -1329,10 +1302,10 @@ public class Issue extends Resource { *
  • summary,comment - include just the summary and comments
  • *
  • *all,-comment - include all fields
  • * - * + * * @param maxResults if non-null, defines the maximum number of * results that can be returned - * + * * @param startAt if non-null, defines the first issue to * return * @@ -1343,66 +1316,52 @@ public class Issue extends Resource { * @throws JiraException when the search fails */ public static SearchResult search(RestClient restclient, String jql, - String includedFields, String expandFields, Integer maxResults, Integer startAt) - throws JiraException { + String includedFields, String expandFields, Integer maxResults, + Integer startAt) throws JiraException { - SearchResult sr = new SearchResult(restclient, jql, includedFields, expandFields, maxResults, startAt); - - return sr; + return new SearchResult( + restclient, + jql, + includedFields, + expandFields, + maxResults, + startAt + ); } - private static JSON executeSearch(RestClient restclient, String jql, - String includedFields, String expandFields, Integer maxResults, - Integer startAt) throws JiraException { - JSON result = null; - - try { - URI searchUri = createSearchURI(restclient, jql, includedFields, - expandFields, maxResults, startAt); - result = restclient.get(searchUri); - } catch (Exception ex) { - throw new JiraException("Failed to search issues", ex); + /** + * Creates the URI to execute a jql search. + * + * @param restclient + * @param jql + * @param includedFields + * @param expandFields + * @param maxResults + * @param startAt + * @return the URI to execute a jql search. + * @throws URISyntaxException + */ + private static URI createSearchURI(RestClient restclient, String jql, + String includedFields, String expandFields, Integer maxResults, + Integer startAt) throws URISyntaxException { + Map queryParams = new HashMap(); + queryParams.put("jql", jql); + if(maxResults != null){ + queryParams.put("maxResults", String.valueOf(maxResults)); + } + if (includedFields != null) { + queryParams.put("fields", includedFields); + } + if (expandFields != null) { + queryParams.put("expand", expandFields); + } + if (startAt != null) { + queryParams.put("startAt", String.valueOf(startAt)); } - if (!(result instanceof JSONObject)) { - throw new JiraException("JSON payload is malformed"); - } - return result; - } - - /** - * Creates the URI to execute a jql search. - * - * @param restclient - * @param jql - * @param includedFields - * @param expandFields - * @param maxResults - * @param startAt - * @return the URI to execute a jql search. - * @throws URISyntaxException - */ - private static URI createSearchURI(RestClient restclient, String jql, - String includedFields, String expandFields, Integer maxResults, - Integer startAt) throws URISyntaxException { - Map queryParams = new HashMap(); - queryParams.put("jql", jql); - if(maxResults != null){ - queryParams.put("maxResults", String.valueOf(maxResults)); - } - if (includedFields != null) { - queryParams.put("fields", includedFields); - } - if (expandFields != null) { - queryParams.put("expand", expandFields); - } - if (startAt != null) { - queryParams.put("startAt", String.valueOf(startAt)); - } - - URI searchUri = restclient.buildURI(getBaseUri() + "search", queryParams); - return searchUri; - } + URI searchUri = restclient.buildURI(getBaseUri() + "search", queryParams); + return searchUri; + } /** * Reloads issue data from the JIRA server (issue includes all navigable @@ -1611,7 +1570,7 @@ public class Issue extends Resource { public User getReporter() { return reporter; } - + public List getRemoteLinks() throws JiraException { JSONArray obj; try { @@ -1696,5 +1655,17 @@ public class Issue extends Resource { return updatedDate; } + public boolean delete(final boolean deleteSubtasks) throws JiraException { + boolean result; + try { + URI uri = restclient.buildURI(getBaseUri() + "issue/" + this.key, new HashMap() {{ + put("deleteSubtasks", String.valueOf(deleteSubtasks)); + }}); + result = (restclient.delete(uri) == null); + } catch (Exception ex) { + throw new JiraException("Failed to delete issue " + key, ex); + } + return result; + } } diff --git a/src/main/java/net/rcarz/jiraclient/JiraClient.java b/src/main/java/net/rcarz/jiraclient/JiraClient.java index 18edb4f..276b8d7 100644 --- a/src/main/java/net/rcarz/jiraclient/JiraClient.java +++ b/src/main/java/net/rcarz/jiraclient/JiraClient.java @@ -50,7 +50,7 @@ public class JiraClient { * @throws JiraException */ public JiraClient(String uri) throws JiraException { - this(uri, null); + this(null, uri, null); } /** @@ -61,17 +61,31 @@ public class JiraClient { * @throws JiraException */ public JiraClient(String uri, ICredentials creds) throws JiraException { - PoolingClientConnectionManager connManager = new PoolingClientConnectionManager(); - connManager.setDefaultMaxPerRoute(20); - connManager.setMaxTotal(40); - HttpClient httpclient = new DefaultHttpClient(connManager); + this(null, uri, creds); + } + + /** + * Creates an authenticated JIRA client with custom HttpClient. + * + * @param httpClient Custom HttpClient to be used + * @param uri Base URI of the JIRA server + * @param creds Credentials to authenticate with + * @throws JiraException + */ + public JiraClient(HttpClient httpClient, String uri, ICredentials creds) throws JiraException { + if (httpClient == null) { + PoolingClientConnectionManager connManager = new PoolingClientConnectionManager(); + connManager.setDefaultMaxPerRoute(20); + connManager.setMaxTotal(40); + httpClient = new DefaultHttpClient(connManager); + } - restclient = new RestClient(httpclient, creds, URI.create(uri)); + restclient = new RestClient(httpClient, creds, URI.create(uri)); if (creds != null) { username = creds.getLogonName(); - //intialize connection if required - creds.initialize(restclient); + //intialize connection if required + creds.initialize(restclient); } } @@ -179,7 +193,7 @@ public class JiraClient { public Issue.SearchResult searchIssues(String jql) throws JiraException { - return Issue.search(restclient, jql, null, null); + return searchIssues(jql, null, null, null, null); } /** @@ -196,7 +210,7 @@ public class JiraClient { public Issue.SearchResult searchIssues(String jql, Integer maxResults) throws JiraException { - return Issue.search(restclient, jql, null, maxResults); + return searchIssues(jql, null, null, maxResults, null); } /** @@ -223,7 +237,7 @@ public class JiraClient { public Issue.SearchResult searchIssues(String jql, String includedFields) throws JiraException { - return Issue.search(restclient, jql, includedFields, null); + return searchIssues(jql, includedFields, null, null, null); } /** @@ -248,10 +262,10 @@ public class JiraClient { * * @throws JiraException when the search fails */ - public Issue.SearchResult searchIssues(String jql, String includedFields, String expandFields) - throws JiraException { + public Issue.SearchResult searchIssues(String jql, String includedFields, + String expandFields) throws JiraException { - return Issue.search(restclient, jql, includedFields, expandFields, null, null); + return searchIssues(jql, includedFields, expandFields, null, null); } /** @@ -279,7 +293,7 @@ public class JiraClient { public Issue.SearchResult searchIssues(String jql, String includedFields, Integer maxResults) throws JiraException { - return Issue.search(restclient, jql, includedFields, maxResults); + return searchIssues(jql, includedFields, null, maxResults, null); } /** @@ -313,8 +327,7 @@ public class JiraClient { public Issue.SearchResult searchIssues(String jql, String includedFields, Integer maxResults, Integer startAt) throws JiraException { - return Issue.search(restclient, jql, includedFields, null, maxResults, - startAt); + return searchIssues(jql, includedFields, null, maxResults, startAt); } /** @@ -347,11 +360,28 @@ public class JiraClient { * * @throws JiraException when the search fails */ - public Issue.SearchResult searchIssues(String jql, String includedFields, String expandFields, - Integer maxResults, Integer startAt) throws JiraException { + public Issue.SearchResult searchIssues(String jql, String includedFields, + String expandFields, Integer maxResults, + Integer startAt) throws JiraException { - return Issue.search(restclient, jql, includedFields, expandFields, maxResults, - startAt); + return Issue.search( + restclient, + jql, + includedFields, + expandFields, + maxResults, + startAt + ); + } + + /** + * Retrieve the jira filter with the supplied id. + * @param id id of the filter to retreive. + * @return The Jira filter with the supplied id + * @throws JiraException + */ + public Filter getFilter(final String id) throws JiraException { + return Filter.get(restclient, id); } /** diff --git a/src/main/java/net/rcarz/jiraclient/Project.java b/src/main/java/net/rcarz/jiraclient/Project.java index f60f5cd..fb674f6 100644 --- a/src/main/java/net/rcarz/jiraclient/Project.java +++ b/src/main/java/net/rcarz/jiraclient/Project.java @@ -41,6 +41,8 @@ public class Project extends Resource { private List issueTypes = null; private List versions = null; private Map roles = null; + private ProjectCategory category = null; + private String email = null; /** * Creates a project from a JSON payload. @@ -73,6 +75,8 @@ public class Project extends Resource { restclient); versions = Field.getResourceArray(Version.class, map.get("versions"), restclient); roles = Field.getMap(String.class, String.class, map.get("roles")); + category = Field.getResource(ProjectCategory.class, map.get( "projectCategory" ), restclient); + email = Field.getString( map.get("email")); } /** @@ -170,5 +174,13 @@ public class Project extends Resource { public Map getRoles() { return roles; } + + public ProjectCategory getCategory() { + return category; + } + + public String getEmail() { + return email; + } } diff --git a/src/main/java/net/rcarz/jiraclient/ProjectCategory.java b/src/main/java/net/rcarz/jiraclient/ProjectCategory.java new file mode 100644 index 0000000..1c7c48a --- /dev/null +++ b/src/main/java/net/rcarz/jiraclient/ProjectCategory.java @@ -0,0 +1,97 @@ +/** + * jira-client - a simple JIRA REST client + * Copyright (c) 2013 Bob Carroll (bob.carroll@alum.rit.edu) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.rcarz.jiraclient; + +import java.util.Map; + +import net.sf.json.JSON; +import net.sf.json.JSONObject; + +/** + * Represents a project category. + */ +public class ProjectCategory extends Resource { + + private String name = null; + private String description = null; + + /** + * Creates a category from a JSON payload. + * + * @param restclient REST client instance + * @param json JSON payload + */ + protected ProjectCategory(RestClient restclient, JSONObject json) { + super(restclient); + + if (json != null) + deserialise(json); + } + + private void deserialise(JSONObject json) { + Map map = json; + + self = Field.getString(map.get("self")); + id = Field.getString(map.get("id")); + description = Field.getString(map.get("description")); + name = Field.getString(map.get("name")); + } + + /** + * Retrieves the given status record. + * + * @param restclient REST client instance + * @param id Internal JIRA ID of the status + * + * @return a status instance + * + * @throws JiraException when the retrieval fails + */ + public static ProjectCategory get(RestClient restclient, String id) + throws JiraException { + + JSON result = null; + + try { + result = restclient.get(getBaseUri() + "projectCategory/" + id); + } catch (Exception ex) { + throw new JiraException("Failed to retrieve status " + id, ex); + } + + if (!(result instanceof JSONObject)) + throw new JiraException("JSON payload is malformed"); + + return new ProjectCategory(restclient, (JSONObject)result); + } + + @Override + public String toString() { + return getName(); + } + + public String getDescription() { + return description; + } + + public String getName() { + return name; + } +} + diff --git a/src/main/java/net/rcarz/jiraclient/WorkLog.java b/src/main/java/net/rcarz/jiraclient/WorkLog.java index 4c4d3f2..50b48c9 100644 --- a/src/main/java/net/rcarz/jiraclient/WorkLog.java +++ b/src/main/java/net/rcarz/jiraclient/WorkLog.java @@ -60,10 +60,10 @@ public class WorkLog extends Resource { id = Field.getString(map.get("id")); author = Field.getResource(User.class, map.get("author"), restclient); comment = Field.getString(map.get("comment")); - created = Field.getDate(map.get("created")); - updated = Field.getDate(map.get("updated")); + created = Field.getDateTime(map.get("created")); + updated = Field.getDateTime(map.get("updated")); updateAuthor = Field.getResource(User.class, map.get("updateAuthor"), restclient); - started = Field.getDate(map.get("started")); + started = Field.getDateTime(map.get("started")); timeSpent = Field.getString(map.get("timeSpent")); timeSpentSeconds = Field.getInteger(map.get("timeSpentSeconds")); } diff --git a/src/test/java/net/rcarz/jiraclient/FilterTest.java b/src/test/java/net/rcarz/jiraclient/FilterTest.java new file mode 100644 index 0000000..a54edfd --- /dev/null +++ b/src/test/java/net/rcarz/jiraclient/FilterTest.java @@ -0,0 +1,29 @@ +package net.rcarz.jiraclient; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Test cases for stuff relating to filters. + */ +public class FilterTest { + + @Test + public void testGetFilter() throws JiraException { + JiraClient jira = new JiraClient("https://jira.atlassian.com/", null); + + String id = "12844"; + Filter filter = jira.getFilter(id); + + assertNotNull(filter); + assertEquals("with id " + id, id, filter.getId()); + final String expectedName = "All JIRA Bugs"; + assertEquals("with name " + expectedName, expectedName, filter.getName()); + final String expectedJql = "project = 10240 AND issuetype = 1 ORDER BY key DESC"; + assertEquals("with jql: " + expectedJql, expectedJql, filter.getJql()); + assertEquals("None favourite", false, filter.isFavourite()); + } + +} diff --git a/src/test/java/net/rcarz/jiraclient/IssueTest.java b/src/test/java/net/rcarz/jiraclient/IssueTest.java index f50e6e6..c95507d 100644 --- a/src/test/java/net/rcarz/jiraclient/IssueTest.java +++ b/src/test/java/net/rcarz/jiraclient/IssueTest.java @@ -5,6 +5,7 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import java.net.URI; import java.util.List; import java.util.Map; @@ -16,6 +17,9 @@ import org.joda.time.DateTimeZone; import org.junit.Assert; import org.junit.Test; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; + public class IssueTest { /** @@ -199,4 +203,16 @@ public class IssueTest { } + /** + * false is bu default so we test positive case only + */ + @Test + public void testDelete() throws Exception { + RestClient restClient = mock(RestClient.class); + URI uri = new URI("DUMMY"); + when(restClient.buildURI(anyString(), any(Map.class))).thenReturn(uri); + when(restClient.delete(eq(uri))).thenReturn(null); + Issue issue = new Issue(restClient, Utils.getTestIssue()); + Assert.assertTrue(issue.delete(true)); + } } diff --git a/src/test/java/net/rcarz/jiraclient/ProjectTest.java b/src/test/java/net/rcarz/jiraclient/ProjectTest.java new file mode 100644 index 0000000..8121f6e --- /dev/null +++ b/src/test/java/net/rcarz/jiraclient/ProjectTest.java @@ -0,0 +1,60 @@ +package net.rcarz.jiraclient; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +public class ProjectTest { + + @Test + public void testCreateProject() { + new Project(null, Utils.getTestProject()); + } + + @Test + public void projectDataTest() + { + String email = "from-jira@example.com"; + String assigneeType = "PROJECT_LEAD"; + String name = "Example"; + String self = "http://www.example.com/jira/rest/api/2/project/EX"; + String id = "10000"; + String key = "EX"; + String description = "This project was created as an example for REST."; + Project project = new Project(null, Utils.getTestProject()); + + assertEquals(description, project.getDescription()); + assertEquals(name, project.getName()); + assertEquals(id, project.getId()); + assertEquals(email, project.getEmail()); + assertEquals(assigneeType, project.getAssigneeType()); + assertTrue(project.getVersions().isEmpty()); + assertEquals(name, project.getName()); + assertEquals(self, project.getSelf()); + assertEquals( key, project.getKey()); + } + @Test + public void projectIssueTypesTest() + { + Project project = new Project(null, Utils.getTestProject()); + assertEquals(2, project.getIssueTypes().size()); + assertEquals("Task", project.getIssueTypes().get(0).getName()); + assertEquals("Bug", project.getIssueTypes().get(1).getName()); + } + @Test + public void projectCategoryTest() + { + String name = "FIRST"; + String id = "10000"; + String description = "First Project Category"; + + Project project = new Project(null, Utils.getTestProject()); + assertNotNull(project.getCategory()); + assertEquals(description, project.getCategory().getDescription()); + + assertEquals(name, project.getCategory().getName()); + assertEquals(id, project.getCategory().getId()); + } +} diff --git a/src/test/java/net/rcarz/jiraclient/SearchTest.java b/src/test/java/net/rcarz/jiraclient/SearchTest.java index 0c0ccac..942841c 100644 --- a/src/test/java/net/rcarz/jiraclient/SearchTest.java +++ b/src/test/java/net/rcarz/jiraclient/SearchTest.java @@ -2,9 +2,13 @@ package net.rcarz.jiraclient; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import java.nio.channels.Pipe; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import static org.junit.Assert.*; public class SearchTest { @@ -21,6 +25,51 @@ public class SearchTest { assertEquals("and resolution Fixed", "Fixed", searchResult.issues.get(0).getResolution().getName()); } + @Test + public void testEmptySearchGivesEmptyResult() throws JiraException { + final JiraClient jira = new JiraClient("https://jira.atlassian.com/", null); + + //Valid jql query that will always yield no results. + final String q = "key = NotExisting-9999999 AND key = blah-8833772"; + Issue.SearchResult searchResult = jira.searchIssues(q); + + final String assertMsg = "Searches that yield no results, should return an empty " + + Issue.SearchResult.class.getSimpleName() + " instance"; + assertTrue(assertMsg, searchResult.issues.isEmpty()); + assertFalse(assertMsg, searchResult.issues.iterator().hasNext()); + assertEquals(assertMsg, 0, searchResult.total); + assertEquals(assertMsg, 0, searchResult.start); + } + + @Test + public void testSearchResultIteratorWithinMaxResultLimit() throws JiraException { + final JiraClient jira = new JiraClient("https://jira.atlassian.com/", null); + final int usedMax = 2; + //Will return everything from the public Jira for Jira + final Issue.SearchResult searchResult = jira.searchIssues("", usedMax); + final List iterResults = new ArrayList(usedMax); + final Iterator iterator = searchResult.iterator(); + for (int i = 0 ; i < usedMax ; i++) { + iterResults.add(iterator.next()); + } + assertEquals(searchResult.issues, iterResults); + } + + @Test + public void testIterateBeyondMaxResult() throws JiraException { + final JiraClient jira = new JiraClient("https://jira.atlassian.com/", null); + + //Will return everything from the public Jira for Jira (at the time of writing 163697 issues). + final int usedMax = 2; + Issue.SearchResult searchResult = jira.searchIssues("", usedMax); + final Iterator iterator = searchResult.iterator(); + System.out.println(searchResult.issues); + for (int i = 0 ; i < 3 ; i++) { + //Running this 3 times without failing, ensures the it can fetch issues beyond the first fetch batch size, as the used max result is only 2. + iterator.next(); + } + } + @Test public void testExpandingChangeLogInSearch() throws JiraException { JiraClient jira = new JiraClient("https://jira.atlassian.com/", null); diff --git a/src/test/java/net/rcarz/jiraclient/Utils.java b/src/test/java/net/rcarz/jiraclient/Utils.java index b06ef1e..1ac16f9 100644 --- a/src/test/java/net/rcarz/jiraclient/Utils.java +++ b/src/test/java/net/rcarz/jiraclient/Utils.java @@ -281,4 +281,116 @@ public class Utils { return jsonObject; } + public static JSONObject getTestProject() { + JSONObject jsonObject = (JSONObject) JSONSerializer.toJSON("{" + + " \"expand\": \"description,lead,url,projectKeys\"," + + " \"self\": \"http://www.example.com/jira/rest/api/2/project/EX\"," + + " \"id\": \"10000\"," + + " \"key\": \"EX\"," + + " \"description\": \"This project was created as an example for REST.\"," + + " \"lead\": {" + + " \"self\": \"http://www.example.com/jira/rest/api/2/user?username=fred\"," + + " \"name\": \"fred\"," + + " \"avatarUrls\": {" + + " \"48x48\": \"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred\"," + + " \"24x24\": \"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred\"," + + " \"16x16\": \"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred\"," + + " \"32x32\": \"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred\"" + + " }," + + " \"displayName\": \"Fred F. User\"," + + " \"active\": false" + + " }," + + " \"components\": [" + + " {" + + " \"self\": \"http://www.example.com/jira/rest/api/2/component/10000\"," + + " \"id\": \"10000\"," + + " \"name\": \"Component 1\"," + + " \"description\": \"This is a JIRA component\"," + + " \"lead\": {" + + " \"self\": \"http://www.example.com/jira/rest/api/2/user?username=fred\"," + + " \"name\": \"fred\"," + + " \"avatarUrls\": {" + + " \"48x48\": \"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred\"," + + " \"24x24\": \"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred\"," + + " \"16x16\": \"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred\"," + + " \"32x32\": \"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred\"" + + " }," + + " \"displayName\": \"Fred F. User\"," + + " \"active\": false" + + " }," + + " \"assigneeType\": \"PROJECT_LEAD\"," + + " \"assignee\": {" + + " \"self\": \"http://www.example.com/jira/rest/api/2/user?username=fred\"," + + " \"name\": \"fred\"," + + " \"avatarUrls\": {" + + " \"48x48\": \"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred\"," + + " \"24x24\": \"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred\"," + + " \"16x16\": \"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred\"," + + " \"32x32\": \"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred\"" + + " }," + + " \"displayName\": \"Fred F. User\"," + + " \"active\": false" + + " }," + + " \"realAssigneeType\": \"PROJECT_LEAD\"," + + " \"realAssignee\": {" + + " \"self\": \"http://www.example.com/jira/rest/api/2/user?username=fred\"," + + " \"name\": \"fred\"," + + " \"avatarUrls\": {" + + " \"48x48\": \"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred\"," + + " \"24x24\": \"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred\"," + + " \"16x16\": \"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred\"," + + " \"32x32\": \"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred\"" + + " }," + + " \"displayName\": \"Fred F. User\"," + + " \"active\": false" + + " }," + + " \"isAssigneeTypeValid\": false," + + " \"project\": \"HSP\"," + + " \"projectId\": 10000" + + " }" + + " ]," + + " \"issueTypes\": [" + + " {" + + " \"self\": \"http://localhost:8090/jira/rest/api/2.0/issueType/3\"," + + " \"id\": \"3\"," + + " \"description\": \"A task that needs to be done.\"," + + " \"iconUrl\": \"http://localhost:8090/jira/images/icons/issuetypes/task.png\"," + + " \"name\": \"Task\"," + + " \"subtask\": false," + + " \"avatarId\": 1" + + " }," + + " {" + + " \"self\": \"http://localhost:8090/jira/rest/api/2.0/issueType/1\"," + + " \"id\": \"1\"," + + " \"description\": \"A problem with the software.\"," + + " \"iconUrl\": \"http://localhost:8090/jira/images/icons/issuetypes/bug.png\"," + + " \"name\": \"Bug\"," + + " \"subtask\": false," + + " \"avatarId\": 10002" + + " }" + + " ]," + + " \"url\": \"http://www.example.com/jira/browse/EX\"," + + " \"email\": \"from-jira@example.com\"," + + " \"assigneeType\": \"PROJECT_LEAD\"," + + " \"versions\": []," + + " \"name\": \"Example\"," + + " \"roles\": {" + + " \"Developers\": \"http://www.example.com/jira/rest/api/2/project/EX/role/10000\"" + + " }," + + " \"avatarUrls\": {" + + " \"48x48\": \"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000\"," + + " \"24x24\": \"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000\"," + + " \"16x16\": \"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000\"," + + " \"32x32\": \"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000\"" + + " }," + + " \"projectCategory\": {" + + " \"self\": \"http://www.example.com/jira/rest/api/2/projectCategory/10000\"," + + " \"id\": \"10000\"," + + " \"name\": \"FIRST\"," + + " \"description\": \"First Project Category\"" + + " }" + + "}"); + return jsonObject; + } + } diff --git a/src/test/java/net/rcarz/jiraclient/WorklogTest.java b/src/test/java/net/rcarz/jiraclient/WorklogTest.java index ab610ad..2d6e12f 100644 --- a/src/test/java/net/rcarz/jiraclient/WorklogTest.java +++ b/src/test/java/net/rcarz/jiraclient/WorklogTest.java @@ -43,12 +43,8 @@ public class WorklogTest { userJSON.put("name","Joseph McCarthy"); mockJSONObject.put("author", userJSON); - String DATE_FORMAT = "yyyy-MM-dd"; - SimpleDateFormat df = new SimpleDateFormat(DATE_FORMAT); - final Date parse = df.parse(dateString, new ParsePosition(0)); - WorkLog workLog = new WorkLog(mockRestClient,mockJSONObject); - assertEquals(parse.toString() + " by Joseph McCarthy",workLog.toString()); + assertEquals(workLog.getCreatedDate() + " by Joseph McCarthy",workLog.toString()); } @Test @@ -63,14 +59,13 @@ public class WorklogTest { assertEquals("45517", workLog.getId()); String author = "joseph"; assertEquals(author, workLog.getAuthor().getName()); - String started = "2015-08-17T00:00:00.000"; - assertEquals(started, simpleDateFormat.format(workLog.getStarted())); - String created = "2015-08-20T00:00:00.000"; - assertEquals(created, simpleDateFormat.format(workLog.getCreatedDate())); + final long expectedStartedUnixTimeStamp = 1439803140000L; //unix timestamp in millis of 2015-08-17T13:19:00.000+0400 + assertEquals(expectedStartedUnixTimeStamp, workLog.getStarted().getTime()); + final long expectedCreatedAndUpdatedUnitTimeStamp = 1440062384000L; //unix timestamp in millis of 2015-08-20T13:19:44.000+0400 + assertEquals(expectedCreatedAndUpdatedUnitTimeStamp, workLog.getCreatedDate().getTime()); + assertEquals(expectedCreatedAndUpdatedUnitTimeStamp, workLog.getUpdatedDate().getTime()); assertEquals(21600, workLog.getTimeSpentSeconds()); assertEquals(author, workLog.getUpdateAuthor().getName()); - assertEquals(created, simpleDateFormat.format(workLog.getUpdatedDate())); - } }