diff --git a/README.md b/README.md index 72e2b79..736fa69 100644 --- a/README.md +++ b/README.md @@ -1,135 +1,7 @@ jira-worklog-query-plugin ========================= -A JIRA plugin to query worklogs and return them in JSON format via an authenticated RESTful service. -There are two main features this plugin provides: +Worklog Query Plugin is a JIRA plugin enabling its users to run worklog queries and apply parameters on these queries through RESTful services. The plugin handles queries according to user roles, therefore, these queries can only be run on worklogs the user has appropriate permissions to access. Various filtering parameters can also be set up to narrow down the search results to a certain interval of time, user, user group and/or project. The result of the worklog query is served in JSON format. -* Querying worklogs. -* Summarize worked time by issues. +The documentation of the plugin can be found [here](https://github.com/everit-org/jira-worklog-query-plugin/wiki). -##Querying worklogs -**_/rest/jira-worklog-query/1.2.0/find/worklogs_** - -Returns worklogs filtered by the given parameters. - -|parameter |value |optional|description| -|----------|------------|--------|-----------| -|startDate |"YYYY-MM-DD"|false |The result will only contain worklogs after this date| -|endDate |"YYYY-MM-DD"|true |The result will only contain worklogs before this date. The default value is the current date.| -|group/user|string |false |A JIRA group or user name whose worklogs are queried. One of them is required.| -|project |string |true |The result will only contain worklogs which are logged to this project. By default, all projects worklogs are returned.| -|fields |string list |true |A list of additional fields to return. Available fields are: comment, updated.| - -Available response representations: - -* 200 - application/json - Example: -``` - [ - [ - { - "id": 10204, - "startDate": "2014-07-31T08:00:00+0200", - "issueKey": "TEST-5", - "userId": "admin", - "duration": 22440 - }, - { - "id": 10207, - "startDate": "2014-07-31T14:14:00+0200", - "issueKey": "TEST-2", - "userId": "admin", - "duration": 2340 - } - ] - ] -``` -* 400 - Returned if there is a problem running the query. - Example: -``` - Error running search: There is no project matching the given 'project' parameter: NOPROJECT -``` - -##Summarize worked time by issues -**_/rest/jira-worklog-query/1.2.0/find/worklogsByIssues_** - -This function queries worklogs - filtered by the given parameters - and summarize them by issues. -The returned issues can be filtered by a JQL expression. -The response are paginated, with default page size of 25 issues. - -|parameter |value |optional|description| -|----------|------------|--------|-----------| -|startDate |"YYYY-MM-DD"|false |The result will only contain worklogs after this date| -|endDate |"YYYY-MM-DD"|false |The result will only contain worklogs before this date. The default value is the current date.| -|group/user|string |false |A JIRA group or user name whose worklogs are queried. One of them is required.| -|jql |string |true |The returned issues are filtered by this JQL expression. By default, all issues are returned.| -|fields |string list |true |The list of fields to return for each issue. By default, no additional fields are returned. [More info](https://docs.atlassian.com/jira/REST/latest/#d2e423)| -|startAt |int |true |The index of the first issue to return (0-based)| -|maxResults|int |true |The maximum number of issues to return (default is 25).| - -Available response representations: - -* 200 - application/json - Example response with no additional fields parameter: -``` - { - "startAt": 0, - "maxResults": 25, - "total": 2, - "issues": [ - { - "id": "13405", - "self": "http://localhost:8080/rest/api/2/issue/13405", - "key": "TEST-1", - "timespent": 1440 - }, - { - "id": "13406", - "self": "http://localhost:8080/rest/api/2/issue/13406", - "key": "TEST-2", - "timespent": 35760 - } - ] - } -``` - - - Example response with "fields=summary,progress" parameter: - -``` - { - "startAt": 0, - "maxResults": 25, - "total": 1, - "issues": [ - { - "id": "13411", - "self": "http://localhost:8080/rest/api/2/issue/13411", - "key": "NEW-1", - "fields": { - "progress": { - "progress": 84000, - "total": 84000, - "percent": 100 - }, - "summary": "new issue1" - }, - "timespent": 67080 - } - ] - } -``` -* 400 - application/json - Returned if there is a problem running the query. - Example: -``` - { - "errorMessages": [ - "Cannot parse the 'startDate' parameter: 2013-0ghjg6-25" - ], - "errors": { - } - } -``` - -[![Analytics](https://ga-beacon.appspot.com/UA-15041869-4/everit-org/jira-worklog-query-plugin-1.x)](https://github.com/igrigorik/ga-beacon) diff --git a/core/pom.xml b/core/pom.xml index 9d538b4..4e83fc1 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -28,7 +28,7 @@ org.everit.jira.worklog.query.plugin - 1.2.0 + 1.2.1-SNAPSHOT org.everit.jira.worklog.query.plugin.parent diff --git a/core/src/main/java/org/everit/jira/worklog/query/plugin/core/DateTimeConverterUtil.java b/core/src/main/java/org/everit/jira/worklog/query/plugin/core/DateTimeConverterUtil.java index b2c25c6..3462e0e 100644 --- a/core/src/main/java/org/everit/jira/worklog/query/plugin/core/DateTimeConverterUtil.java +++ b/core/src/main/java/org/everit/jira/worklog/query/plugin/core/DateTimeConverterUtil.java @@ -32,95 +32,97 @@ */ public final class DateTimeConverterUtil { - /** - * The date format of the input parameters. - */ - private static final String INPUT_DATE_FORMAT = "yyyy-MM-dd"; + /** + * The date format of the input parameters. + */ + private static final String INPUT_DATE_FORMAT = "yyyy-MM-dd"; - /** - * The date format of JIRA. - */ - private static final String JIRA_OUTPUT_DATE_TIME_FORMAT = "yyyy-MM-dd hh:mm:ss.s"; + /** + * The date format of JIRA. + */ + private static final String JIRA_OUTPUT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.s"; - /** - * The date format of the output. - */ - private static final String OUTPUT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; + /** + * The date format of the output. + */ + private static final String OUTPUT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; - /** - * Convert the date to String ({@value #OUTPUT_DATE_TIME_FORMAT}). - * - * @param date - * The Date to convert. - * @return The result time. - */ - private static String dateToString(final Date date) { - SimpleDateFormat simpleDateFormat = new SimpleDateFormat(OUTPUT_DATE_TIME_FORMAT); - String dateString = simpleDateFormat.format(date); - return dateString; - } + /** + * Convert the date to String ({@value #OUTPUT_DATE_TIME_FORMAT}). + * + * @param date + * The Date to convert. + * @return The result time. + */ + private static String dateToString(final Date date) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(OUTPUT_DATE_TIME_FORMAT); + String dateString = simpleDateFormat.format(date); + return dateString; + } - /** - * Convert String ({@value #INPUT_DATE_FORMAT}) to Calendar. - * - * @param dateString - * The String date to convert. - * @return The result Date. - * @throws ParseException - * If can't parse the date. - */ - public static Calendar inputStringToCalendar(final String dateString) throws ParseException { - DateFormat dateFormat = new SimpleDateFormat(INPUT_DATE_FORMAT); - Calendar calendar = Calendar.getInstance(); - calendar.setTime(dateFormat.parse(dateString)); - return calendar; - } + /** + * Convert String ({@value #INPUT_DATE_FORMAT}) to Calendar. + * + * @param dateString + * The String date to convert. + * @return The result Date. + * @throws ParseException + * If can't parse the date. + */ + public static Calendar inputStringToCalendar(final String dateString) throws ParseException { + DateFormat dateFormat = new SimpleDateFormat(INPUT_DATE_FORMAT); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(dateFormat.parse(dateString)); + return calendar; + } - /** - * Set the calendar hour, minute and second value. - * - * @param originalCalendar - * The original calendar. - * @param hourOfDay - * The hour of the day to set. - * @param minute - * The minute to set. - * @param second - * The second to set. - * @return The new calendar object. - */ - public static Calendar setCalendarHourMinSec(final Calendar originalCalendar, final int hourOfDay, - final int minute, final int second) { - Calendar calendar = Calendar.getInstance(); - calendar.set( - originalCalendar.get(Calendar.YEAR), - originalCalendar.get(Calendar.MONTH), - originalCalendar.get(Calendar.DAY_OF_MONTH), - hourOfDay, - minute, - second); - return calendar; - } + /** + * Set the calendar hour, minute and second value. + * + * @param originalCalendar + * The original calendar. + * @param hourOfDay + * The hour of the day to set. + * @param minute + * The minute to set. + * @param second + * The second to set. + * @return The new calendar object. + */ + public static Calendar setCalendarHourMinSec(final Calendar originalCalendar, + final int hourOfDay, + final int minute, final int second) { + Calendar calendar = Calendar.getInstance(); + calendar.set( + originalCalendar.get(Calendar.YEAR), + originalCalendar.get(Calendar.MONTH), + originalCalendar.get(Calendar.DAY_OF_MONTH), + hourOfDay, + minute, + second); + return calendar; + } - /** - * Format a String date to valid ISO-8601 format String date. - * - * @param dateString - * The date. - * @return The formated String date. - * @throws ParseException - * If cannot parse the String to Date. - */ - public static String stringDateToISO8601FormatString(final String dateString) throws ParseException { - DateFormat dateFormat = new SimpleDateFormat(JIRA_OUTPUT_DATE_TIME_FORMAT); - Date date = dateFormat.parse(dateString); - return DateTimeConverterUtil.dateToString(date); - } + /** + * Format a String date to valid ISO-8601 format String date. + * + * @param dateString + * The date. + * @return The formated String date. + * @throws ParseException + * If cannot parse the String to Date. + */ + public static String stringDateToISO8601FormatString(final String dateString) + throws ParseException { + DateFormat dateFormat = new SimpleDateFormat(JIRA_OUTPUT_DATE_TIME_FORMAT); + Date date = dateFormat.parse(dateString); + return DateTimeConverterUtil.dateToString(date); + } - /** - * Private constructor. - */ - private DateTimeConverterUtil() { - } + /** + * Private constructor. + */ + private DateTimeConverterUtil() { + } } diff --git a/core/src/main/java/org/everit/jira/worklog/query/plugin/core/WorklogQueryResource.java b/core/src/main/java/org/everit/jira/worklog/query/plugin/core/WorklogQueryResource.java index c139f71..6382727 100644 --- a/core/src/main/java/org/everit/jira/worklog/query/plugin/core/WorklogQueryResource.java +++ b/core/src/main/java/org/everit/jira/worklog/query/plugin/core/WorklogQueryResource.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -92,771 +93,847 @@ import com.atlassian.jira.web.bean.PagerFilter; /** - * The WorklogQueryResource class. The class contains the findWorklogs method. The class grant the JIRA worklog query. + * The WorklogQueryResource class. The class contains the findWorklogs method. The class grant the + * JIRA worklog query. * * @param */ @Path("/find") public class WorklogQueryResource { - private class IssueBeanWithTimespent extends IssueBean { - @XmlElement - private Long timespent = 0L; + private class IssueBeanWithTimespent extends IssueBean { + @XmlElement + private Long timespent = 0L; - public IssueBeanWithTimespent(final Long id, final String key, final URI selfUri, final Long timespent) - { - super(id, key, selfUri); - this.timespent = timespent; - } - - public Long getTimeSpent() { - return timespent; - } + public IssueBeanWithTimespent(final Long id, final String key, final URI selfUri, + final Long timespent) + { + super(id, key, selfUri); + this.timespent = timespent; } - @XmlRootElement - @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) - private class SearchResultsBeanWithTimespent extends SearchResultsBean { - public List issues; - - public SearchResultsBeanWithTimespent(final Integer startAt, final Integer maxResults, final Integer total, - final List issues) - { - this.startAt = startAt; - this.maxResults = maxResults; - this.total = total; - this.issues = issues; - } + public Long getTimeSpent() { + return timespent; } + } - /** - * The logger used to log. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(WorklogQueryResource.class); - - /** - * The last hour of a day. - */ - private static final int LAST_HOUR_OF_DAY = 23; + @XmlRootElement + @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) + private class SearchResultsBeanWithTimespent extends SearchResultsBean { + public List issues; - /** - * The last minute of an hour. - */ - private static final int LAST_MINUTE_OF_HOUR = 59; - - /** - * The last second of a minute. - */ - private static final int LAST_SECOND_OF_MINUTE = 59; - - private static final int DEFAULT_STARTAT_PARAM = 0; - private static final int DEFAULT_MAXRESULT_PARAM = 25; - - private void addFields(final Issue issue, final IssueBean bean) + public SearchResultsBeanWithTimespent(final Integer startAt, final Integer maxResults, + final Integer total, + final List issues) { - // iterate over all the visible layout items from the field layout for this issue and attempt to add them - // to the result - final User loggedInUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser(); - final FieldLayout layout = ComponentAccessor.getFieldLayoutManager().getFieldLayout(issue); - final List fieldLayoutItems = layout.getVisibleLayoutItems( - loggedInUser, issue.getProjectObject(), - CollectionBuilder.list(issue.getIssueTypeObject().getId())); - for (final FieldLayoutItem fieldLayoutItem : fieldLayoutItems) - { - final OrderableField field = fieldLayoutItem.getOrderableField(); - final FieldJsonRepresentation fieldValue = getFieldValue(fieldLayoutItem, issue); - if ((fieldValue != null) && (fieldValue.getStandardData() != null)) - { - bean.addField(field, fieldValue, false); - } - } - // Then we try to add "NavigableFields" which aren't "OrderableFields" unless they ae special ones. - // These aren't included in the Field Layout. - // This is a bit crap because "getAvailableNavigableFields" doesn't take the issue into account. - // All it means is the field is not hidden in at least one project the user has BROWSE permission on. - try + this.startAt = startAt; + this.maxResults = maxResults; + this.total = total; + this.issues = issues; + } + } + + private static final int DEFAULT_MAXRESULT_PARAM = 25; + + private static final int DEFAULT_STARTAT_PARAM = 0; + + /** + * The last hour of a day. + */ + private static final int LAST_HOUR_OF_DAY = 23; + + /** + * The last minute of an hour. + */ + private static final int LAST_MINUTE_OF_HOUR = 59; + + /** + * The last second of a minute. + */ + private static final int LAST_SECOND_OF_MINUTE = 59; + /** + * The logger used to log. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(WorklogQueryResource.class); + + private void addFields(final Issue issue, final IssueBean bean) + { + // iterate over all the visible layout items from the field layout for this issue and attempt to + // add them + // to the result + final User loggedInUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser(); + final FieldLayout layout = ComponentAccessor.getFieldLayoutManager().getFieldLayout(issue); + final List fieldLayoutItems = layout.getVisibleLayoutItems( + loggedInUser, issue.getProjectObject(), + CollectionBuilder.list(issue.getIssueTypeObject().getId())); + for (final FieldLayoutItem fieldLayoutItem : fieldLayoutItems) + { + final OrderableField field = fieldLayoutItem.getOrderableField(); + final FieldJsonRepresentation fieldValue = getFieldValue(fieldLayoutItem, issue); + if ((fieldValue != null) && (fieldValue.getStandardData() != null)) + { + bean.addField(field, fieldValue, false); + } + } + // Then we try to add "NavigableFields" which aren't "OrderableFields" unless they ae special + // ones. + // These aren't included in the Field Layout. + // This is a bit crap because "getAvailableNavigableFields" doesn't take the issue into account. + // All it means is the field is not hidden in at least one project the user has BROWSE + // permission on. + try + { + final Set fields = ComponentAccessor.getFieldManager() + .getAvailableNavigableFields(loggedInUser); + for (NavigableField field : fields) + { + if (!bean.hasField(field.getId())) { - final Set fields = ComponentAccessor.getFieldManager().getAvailableNavigableFields( - loggedInUser); - for (NavigableField field : fields) + if (!(field instanceof OrderableField) || (field instanceof ProjectSystemField)) + { + if (field instanceof RestAwareField) { - if (!bean.hasField(field.getId())) - { - if (!(field instanceof OrderableField) || (field instanceof ProjectSystemField)) - { - if (field instanceof RestAwareField) - { - addRestAwareField(issue, bean, field, (RestAwareField) field); - } - } - } + addRestAwareField(issue, bean, field, (RestAwareField) field); } - } catch (FieldException e) - { - // ignored...display as much as we can. + } } + } + } catch (FieldException e) + { + // ignored...display as much as we can. } + } - private void addRestAwareField(final Issue issue, final IssueBean bean, final Field field, - final RestAwareField restAware) + private void addRestAwareField(final Issue issue, final IssueBean bean, final Field field, + final RestAwareField restAware) + { + FieldJsonRepresentation fieldJsonFromIssue = restAware.getJsonFromIssue(issue, false, null); + if ((fieldJsonFromIssue != null) && (fieldJsonFromIssue.getStandardData() != null)) { - FieldJsonRepresentation fieldJsonFromIssue = restAware.getJsonFromIssue(issue, false, null); - if ((fieldJsonFromIssue != null) && (fieldJsonFromIssue.getStandardData() != null)) - { - bean.addField(field, fieldJsonFromIssue, false); - } + bean.addField(field, fieldJsonFromIssue, false); } - - /** - * Check the required (or optional) parameters. If any parameter missing or conflict return with the right Response - * what describes the problem. If everything is right then return with null. - * - * @param startDate - * The startDate parameter. - * @param endDate - * The endDate parameter. - * @param user - * The user parameter. - * @param group - * The group parameter. - * - * @return If a bad parameter was found then return with Response else null. - */ - private void checkRequiredFindWorklogsByIssuesParameter(final String startDate, final String endDate, - final String user, final String group) { - if (isStringEmpty(startDate)) { - throw new RESTException(Response.Status.BAD_REQUEST, "The 'startDate' parameter is missing!"); - } - if (isStringEmpty(endDate)) { - throw new RESTException(Response.Status.BAD_REQUEST, "The 'endDate' parameter is missing!"); - } - if ((isStringEmpty(user)) && (isStringEmpty(group))) { - throw new RESTException(Response.Status.BAD_REQUEST, "The 'user' or the 'group' parameter is missing!"); - } - if ((!isStringEmpty(user)) && (!isStringEmpty(group))) { - throw new RESTException(Response.Status.BAD_REQUEST, - "The 'user' and the 'group' parameters cannot be present at the same time."); - } + } + + /** + * Check the required (or optional) parameters. If any parameter missing or conflict return with + * the right Response what describes the problem. If everything is right then return with null. + * + * @param startDate + * The startDate parameter. + * @param endDate + * The endDate parameter. + * @param user + * The user parameter. + * @param group + * The group parameter. + * + * @return If a bad parameter was found then return with Response else null. + */ + private void checkRequiredFindWorklogsByIssuesParameter(final String startDate, + final String endDate, + final String user, final String group) { + if (isStringEmpty(startDate)) { + throw new RESTException(Response.Status.BAD_REQUEST, "The 'startDate' parameter is missing!"); + } + if (isStringEmpty(endDate)) { + throw new RESTException(Response.Status.BAD_REQUEST, "The 'endDate' parameter is missing!"); + } + if ((isStringEmpty(user)) && (isStringEmpty(group))) { + throw new RESTException(Response.Status.BAD_REQUEST, + "The 'user' or the 'group' parameter is missing!"); + } + if ((!isStringEmpty(user)) && (!isStringEmpty(group))) { + throw new RESTException(Response.Status.BAD_REQUEST, + "The 'user' and the 'group' parameters cannot be present at the same time."); + } + } + + /** + * Check the required (or optional) parameters. If any parameter missing or conflict return with + * the right Response what describes the problem. If everything is right then return with null. + * + * @param startDate + * The findWorklogs startDate parameter. + * @param user + * The findWorklogs user parameter. + * @param group + * The findWorklogs group parameter. + * @return If find bad parameter then return with Response else null. + */ + private Response checkRequiredFindWorklogsParameter(final String startDate, final String user, + final String group) { + if (isStringEmpty(startDate)) { + return Response.status(Response.Status.BAD_REQUEST). + entity("The 'startDate' parameter is missing!").build(); + } + if ((isStringEmpty(user)) && (isStringEmpty(group))) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("The 'user' or the 'group' parameter is missing!").build(); + } + if ((!isStringEmpty(user)) && (!isStringEmpty(group))) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("The 'user' and the 'group' parameters cannot be present at the same time.") + .build(); + } + return null; + } + + /** + * Convert the endDate String to Calendar. + * + * @param endDateString + * The endDate parameter. + * @return The formated, valid calendar. + * @throws ParseException + * If cannot parse the String to Calendar. + */ + private Calendar convertEndDate(final String endDateString) throws ParseException { + Calendar endDate; + if ((endDateString == null) || (endDateString.length() == 0)) { + endDate = Calendar.getInstance(); + } else { + endDate = DateTimeConverterUtil.inputStringToCalendar(endDateString); + } + endDate = DateTimeConverterUtil.setCalendarHourMinSec(endDate, + LAST_HOUR_OF_DAY, LAST_MINUTE_OF_HOUR, LAST_SECOND_OF_MINUTE); + return endDate; + } + + /** + * Convert the startDate String to Calendar. + * + * @param startDateString + * The startDate parameter. + * @return The formated, valid calendar. + * @throws ParseException + * Id cannot parse the String to Calendar. + */ + private Calendar convertStartDate(final String startDateString) throws ParseException { + Calendar startDate = DateTimeConverterUtil.inputStringToCalendar(startDateString); + startDate = DateTimeConverterUtil.setCalendarHourMinSec(startDate, 0, 0, 0); + return startDate; + } + + /** + * Creates a list of project Id's. Filtering based on project permission and the query + * projectString parameter. + * + * @param projectString + * The query projectString parameter. + * @param user + * The logged user. + * + * @return The list of the issues conditions. + * @throws GenericEntityException + * If the GenericDelegator throw a GenericEntityException. + */ + private List createProjects(final String projectString, final User user) { + + Collection projects = ComponentAccessor.getPermissionManager() + .getProjectObjects(Permissions.BROWSE, user); + + List projectList = new ArrayList(); + for (Project project : projects) { + if ((projectString != null) && (projectString.length() != 0)) { + if (projectString.equals(project.getKey())) { + projectList.add(project.getId()); + } + } else { + projectList.add(project.getId()); + } + } + return projectList; + } + + /** + * Creates a list of user's. If the group variable is defined, then collect all of the user's keys + * in that group. If userName is defined then add the users key to the list. + * + * @param userName + * the user name of the user + * @param group + * the name of the group + * @return + */ + private List createUsers(final String userName, final String group) { + List users = new ArrayList(); + if ((group != null) && (group.length() != 0)) { + Set groupUsers = ComponentAccessor.getUserUtil().getAllUsersInGroupNames( + Arrays.asList(new String[] { group })); + Set assigneeIds = new TreeSet(); + for (User groupUser : groupUsers) { + assigneeIds.add(groupUser.getName()); + String userKey = UserCompatibilityHelper.getKeyForUser(groupUser); + users.add(userKey); + } + } else if ((userName != null) && (userName.length() != 0)) { + User user = ComponentAccessor.getUserUtil().getUserObject(userName); + if (user != null) { + String userKey = UserCompatibilityHelper.getKeyForUser(user); + users.add(userKey); + } + } + return users; + } + + /** + * Convert a ResultSet object to a JSonObject. + * + * @param rs + * The ResultSet worklog. + * @param fields + * @return The worklog JSonObject. + * + * @throws JSONException + * If can't put value to the JSonObject. + * @throws ParseException + * If ParserException when parse the startDate. + */ + private JSONObject createWorklogJSONObject(final ResultSet rs, final List fields) + throws JSONException, + SQLException, ParseException { + JSONObject jsonWorklog = new JSONObject(); + jsonWorklog.put("id", rs.getLong("id")); + + Timestamp sDate = rs.getTimestamp("startdate"); + jsonWorklog.put("startDate", + DateTimeConverterUtil.stringDateToISO8601FormatString(sDate.toString())); + + IssueManager issueManager = ComponentAccessor.getIssueManager(); + String issueKey = issueManager.getIssueObject(rs.getLong("issueid")).getKey(); + jsonWorklog.put("issueKey", issueKey); + + String userKey = rs.getString("author"); + User user = UserCompatibilityHelper.getUserForKey(userKey); + String userName = user.getName(); + jsonWorklog.put("userId", userName); + + long timeSpentInSec = rs.getLong("timeworked"); + jsonWorklog.put("duration", timeSpentInSec); + + if (fields != null) { + if (StringList.joinLists(fields).asList().contains("comment")) { + String comment = rs.getString("worklogbody"); + jsonWorklog.put("comment", comment); + } + if (StringList.joinLists(fields).asList().contains("updated")) { + Timestamp updated = rs.getTimestamp("updated"); + jsonWorklog.put("updated", + DateTimeConverterUtil.stringDateToISO8601FormatString(updated.toString())); + } + } + return jsonWorklog; + } + + /** + * The updatedWorklogs restful api method. + * + * @param startDate + * The query startDate parameter. + * @param endDate + * The query endDate parameter, optional. Default value is the current time. + * @param user + * The query user parameter, optional. This or the group parameter is required. + * @param group + * The query group parameter, optional. This or the user parameter is required. + * @param project + * The query project parameter, optional. Default is all project. + * @return {@link Response} what contains the result of the query. If the method parameters was + * wrong then a message what contains the description of the bad request. In case of any + * exception return {@link Response} with INTERNAL_SERVER_ERROR status what contains the + * original exception message. + */ + @GET + @Produces("*/*") + @Path("/updatedWorklogs") + public Response findUpdatedWorklogs( + @QueryParam("startDate") final String startDate, + @QueryParam("endDate") final String endDate, + @QueryParam("user") final String user, + @QueryParam("group") final String group, + @QueryParam("project") final String project, + @QueryParam("fields") final List fields) { + + Response checkRequiredFindWorklogsParamResponse = checkRequiredFindWorklogsParameter(startDate, + user, group); + if (checkRequiredFindWorklogsParamResponse != null) { + return checkRequiredFindWorklogsParamResponse; + } + Calendar startDateCalendar; + try { + startDateCalendar = convertStartDate(startDate); + } catch (ParseException e) { + LOGGER.debug("Failed to convert start date", e); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Cannot parse the 'startDate' parameter: " + startDate).build(); + } + Calendar endDateCalendar; + try { + endDateCalendar = convertEndDate(endDate); + } catch (ParseException e) { + LOGGER.debug("Failed to convert end date", e); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Cannot parse the 'endDate' parameter: " + endDate).build(); + } + try { + return Response.ok( + worklogQuery(startDateCalendar, endDateCalendar, user, group, project, fields, true)) + .build(); + } catch (Exception e) { + LOGGER.error("Failed to query the worklogs", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(e.getMessage()).build(); + } + } + + /** + * The worklogs restful api method. + * + * @param startDate + * The query startDate parameter. + * @param endDate + * The query endDate parameter, optional. Default value is the current time. + * @param user + * The query user parameter, optional. This or the group parameter is required. + * @param group + * The query group parameter, optional. This or the user parameter is required. + * @param project + * The query project parameter, optional. Default is all project. + * @return {@link Response} what contains the result of the query. If the method parameters was + * wrong then a message what contains the description of the bad request. In case of any + * exception return {@link Response} with INTERNAL_SERVER_ERROR status what contains the + * original exception message. + */ + @GET + @Produces({ MediaType.APPLICATION_JSON }) + @Path("/worklogs") + public Response findWorklogs( + @QueryParam("startDate") final String startDate, + @QueryParam("endDate") final String endDate, + @QueryParam("user") final String user, + @QueryParam("group") final String group, + @QueryParam("project") final String project, + @QueryParam("fields") final List fields) { + + Response checkRequiredFindWorklogsParamResponse = checkRequiredFindWorklogsParameter(startDate, + user, group); + if (checkRequiredFindWorklogsParamResponse != null) { + return checkRequiredFindWorklogsParamResponse; + } + Calendar startDateCalendar; + try { + startDateCalendar = convertStartDate(startDate); + } catch (ParseException e) { + LOGGER.debug("Failed to convert start date", e); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Cannot parse the 'startDate' parameter: " + startDate).build(); + } + Calendar endDateCalendar; + try { + endDateCalendar = convertEndDate(endDate); + } catch (ParseException e) { + LOGGER.debug("Failed to convert end date", e); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Cannot parse the 'endDate' parameter: " + endDate).build(); + } + try { + return worklogQuery(startDateCalendar, endDateCalendar, user, group, project, fields, false); + } catch (Exception e) { + LOGGER.error("Failed to query the worklogs", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(e.getMessage()).build(); + } + } + + @GET + @Path("/worklogsByIssues") + @Produces({ MediaType.APPLICATION_JSON }) + public SearchResultsBeanWithTimespent findWorklogsByIssues( + @QueryParam("startDate") final String startDate, + @QueryParam("endDate") final String endDate, + @QueryParam("user") final String user, + @QueryParam("group") final String group, + @QueryParam("jql") String jql, + @DefaultValue("0") @QueryParam("startAt") int startAt, + @DefaultValue("25") @QueryParam("maxResults") int maxResults, + @DefaultValue("emptyFieldValue") @QueryParam("fields") final List fields) + throws + URISyntaxException, SQLException { + + checkRequiredFindWorklogsByIssuesParameter(startDate, endDate, user, group); + + Calendar startDateCalendar = null; + try { + startDateCalendar = convertStartDate(startDate); + } catch (ParseException e) { + throw new RESTException(Response.Status.BAD_REQUEST, + "Cannot parse the 'startDate' parameter: " + startDate); + } + Calendar endDateCalendar = null; + try { + endDateCalendar = convertEndDate(endDate); + } catch (ParseException e) { + throw new RESTException(Response.Status.BAD_REQUEST, "Cannot parse the 'endDate' parameter: " + + endDate); + } + if (startAt < 0) { + startAt = DEFAULT_STARTAT_PARAM; + } + if (maxResults < 0) { + maxResults = DEFAULT_MAXRESULT_PARAM; + } + List users = createUsers(user, group); + if (users.isEmpty()) { + throw new RESTException(Response.Status.BAD_REQUEST, + "Error running search: There is no group or user matching the given parameters."); + } + if (jql == null) { + jql = ""; + } + List issues = null; + try { + issues = getIssuesByJQL(jql); + } catch (SearchException e) { + throw new RESTException(Response.Status.BAD_REQUEST, "Error running search: " + e); + } catch (JqlParseException e) { + throw new RESTException(Response.Status.BAD_REQUEST, e.getMessage()); } - /** - * Check the required (or optional) parameters. If any parameter missing or conflict return with the right Response - * what describes the problem. If everything is right then return with null. - * - * @param startDate - * The findWorklogs startDate parameter. - * @param user - * The findWorklogs user parameter. - * @param group - * The findWorklogs group parameter. - * @return If find bad parameter then return with Response else null. - */ - private Response checkRequiredFindWorklogsParameter(final String startDate, final String user, final String group) { - if (isStringEmpty(startDate)) { - return Response.status(Response.Status.BAD_REQUEST). - entity("The 'startDate' parameter is missing!").build(); - } - if ((isStringEmpty(user)) && (isStringEmpty(group))) { - return Response.status(Response.Status.BAD_REQUEST) - .entity("The 'user' or the 'group' parameter is missing!").build(); - } - if ((!isStringEmpty(user)) && (!isStringEmpty(group))) { - return Response.status(Response.Status.BAD_REQUEST) - .entity("The 'user' and the 'group' parameters cannot be present at the same time.").build(); - } - return null; - } - - /** - * Convert the endDate String to Calendar. - * - * @param endDateString - * The endDate parameter. - * @return The formated, valid calendar. - * @throws ParseException - * If cannot parse the String to Calendar. - */ - private Calendar convertEndDate(final String endDateString) throws ParseException { - Calendar endDate; - if ((endDateString == null) || (endDateString.length() == 0)) { - endDate = Calendar.getInstance(); - } else { - endDate = DateTimeConverterUtil.inputStringToCalendar(endDateString); - } - endDate = DateTimeConverterUtil.setCalendarHourMinSec(endDate, - LAST_HOUR_OF_DAY, LAST_MINUTE_OF_HOUR, LAST_SECOND_OF_MINUTE); - return endDate; - } - - /** - * Convert the startDate String to Calendar. - * - * @param startDateString - * The startDate parameter. - * @return The formated, valid calendar. - * @throws ParseException - * Id cannot parse the String to Calendar. - */ - private Calendar convertStartDate(final String startDateString) throws ParseException { - Calendar startDate = DateTimeConverterUtil.inputStringToCalendar(startDateString); - startDate = DateTimeConverterUtil.setCalendarHourMinSec(startDate, 0, 0, 0); - return startDate; - } - - /** - * Creates a list of project Id's. Filtering based on project permission and the query projectString parameter. - * - * @param projectString - * The query projectString parameter. - * @param user - * The logged user. - * - * @return The list of the issues conditions. - * @throws GenericEntityException - * If the GenericDelegator throw a GenericEntityException. - */ - private List createProjects(final String projectString, final User user) { - - List projects = (List) ComponentAccessor.getPermissionManager().getProjectObjects( - Permissions.BROWSE, - user); - - List projectList = new ArrayList(); - for (Project project : projects) { - if ((projectString != null) && (projectString.length() != 0)) { - if (projectString.equals(project.getKey())) { - projectList.add(project.getId()); - } - } else { - projectList.add(project.getId()); - } - } - return projectList; - } - - /** - * Creates a list of user's. If the group variable is defined, then collect all of the user's keys in that group. If - * userName is defined then add the users key to the list. - * - * @param userName - * the user name of the user - * @param group - * the name of the group - * @return - */ - private List createUsers(final String userName, final String group) { - List users = new ArrayList(); - if ((group != null) && (group.length() != 0)) { - Set groupUsers = ComponentAccessor.getUserUtil().getAllUsersInGroupNames( - Arrays.asList(new String[] { group })); - Set assigneeIds = new TreeSet(); - for (User groupUser : groupUsers) { - assigneeIds.add(groupUser.getName()); - String userKey = UserCompatibilityHelper.getKeyForUser(groupUser); - users.add(userKey); - } - } else if ((userName != null) && (userName.length() != 0)) { - User user = ComponentAccessor.getUserUtil().getUserObject(userName); - if (user != null) { - String userKey = UserCompatibilityHelper.getKeyForUser(user); - users.add(userKey); - } - } - return users; - } - - /** - * Convert a ResultSet object to a JSonObject. - * - * @param rs - * The ResultSet worklog. - * @param fields - * @return The worklog JSonObject. - * - * @throws JSONException - * If can't put value to the JSonObject. - * @throws ParseException - * If ParserException when parse the startDate. - */ - private JSONObject createWorklogJSONObject(final ResultSet rs, final List fields) throws JSONException, - SQLException, ParseException { - JSONObject jsonWorklog = new JSONObject(); - jsonWorklog.put("id", rs.getLong("id")); - - Timestamp sDate = rs.getTimestamp("startdate"); - jsonWorklog.put("startDate", DateTimeConverterUtil.stringDateToISO8601FormatString(sDate.toString())); - - IssueManager issueManager = ComponentAccessor.getIssueManager(); - String issueKey = issueManager.getIssueObject(rs.getLong("issueid")).getKey(); - jsonWorklog.put("issueKey", issueKey); - - String userKey = rs.getString("author"); - User user = UserCompatibilityHelper.getUserForKey(userKey); - String userName = user.getName(); - jsonWorklog.put("userId", userName); - - long timeSpentInSec = rs.getLong("timeworked"); - jsonWorklog.put("duration", timeSpentInSec); - - if (fields != null) { - if (StringList.joinLists(fields).asList().contains("comment")) { - String comment = rs.getString("worklogbody"); - jsonWorklog.put("comment", comment); - } - if (StringList.joinLists(fields).asList().contains("updated")) { - Timestamp updated = rs.getTimestamp("updated"); - jsonWorklog.put("updated", DateTimeConverterUtil.stringDateToISO8601FormatString(updated.toString())); - } - } - return jsonWorklog; - } - - /** - * The updatedWorklogs restful api method. - * - * @param startDate - * The query startDate parameter. - * @param endDate - * The query endDate parameter, optional. Default value is the current time. - * @param user - * The query user parameter, optional. This or the group parameter is required. - * @param group - * The query group parameter, optional. This or the user parameter is required. - * @param project - * The query project parameter, optional. Default is all project. - * @return {@link Response} what contains the result of the query. If the method parameters was wrong then a message - * what contains the description of the bad request. In case of any exception return {@link Response} with - * INTERNAL_SERVER_ERROR status what contains the original exception message. - */ - @GET - @Produces("*/*") - @Path("/updatedWorklogs") - public Response findUpdatedWorklogs( - @QueryParam("startDate") final String startDate, - @QueryParam("endDate") final String endDate, - @QueryParam("user") final String user, - @QueryParam("group") final String group, - @QueryParam("project") final String project, - @QueryParam("fields") final List fields) { - - Response checkRequiredFindWorklogsParamResponse = checkRequiredFindWorklogsParameter(startDate, user, group); - if (checkRequiredFindWorklogsParamResponse != null) { - return checkRequiredFindWorklogsParamResponse; - } - Calendar startDateCalendar; - try { - startDateCalendar = convertStartDate(startDate); - } catch (ParseException e) { - LOGGER.debug("Failed to convert start date", e); - return Response.status(Response.Status.BAD_REQUEST) - .entity("Cannot parse the 'startDate' parameter: " + startDate).build(); - } - Calendar endDateCalendar; - try { - endDateCalendar = convertEndDate(endDate); - } catch (ParseException e) { - LOGGER.debug("Failed to convert end date", e); - return Response.status(Response.Status.BAD_REQUEST) - .entity("Cannot parse the 'endDate' parameter: " + endDate).build(); - } - try { - return Response.ok(worklogQuery(startDateCalendar, endDateCalendar, user, group, project, fields, true)) - .build(); - } catch (Exception e) { - LOGGER.error("Failed to query the worklogs", e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(e.getMessage()).build(); - } + List issueIds = new ArrayList(); + for (Issue issue : issues) { + issueIds.add(issue.getId()); } + Collections.reverse(issues); + + Map result = sumWorklogs(startDateCalendar, endDateCalendar, users, issueIds); + + IncludedFields includedFields = IncludedFields.includeNavigableByDefault(fields); + String baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL) + + "/rest/api/2/issue/"; + boolean isEmptyField = StringList.joinLists(fields).asList().contains("emptyFieldValue"); + List issueBeans = new ArrayList(); + for (int i = 0, j = 0; ((j < issues.size()) && (i < (startAt + maxResults)));) { + Issue issue = issues.get(j); + if (result.containsKey(issue.getId())) { + if ((i >= startAt)) { + IssueBeanWithTimespent bean = new IssueBeanWithTimespent(issue.getId(), issue.getKey(), + new URI(baseUrl + issue.getId()), result.get(issue.getId())); + bean.fieldsToInclude(includedFields); + if (!isEmptyField) { + addFields(issue, bean); + } + issueBeans.add(bean); + } + i++; + } + j++; + } + SearchResultsBeanWithTimespent searchResultsBean = new SearchResultsBeanWithTimespent(startAt, + maxResults, + result.size(), issueBeans); - /** - * The worklogs restful api method. - * - * @param startDate - * The query startDate parameter. - * @param endDate - * The query endDate parameter, optional. Default value is the current time. - * @param user - * The query user parameter, optional. This or the group parameter is required. - * @param group - * The query group parameter, optional. This or the user parameter is required. - * @param project - * The query project parameter, optional. Default is all project. - * @return {@link Response} what contains the result of the query. If the method parameters was wrong then a message - * what contains the description of the bad request. In case of any exception return {@link Response} with - * INTERNAL_SERVER_ERROR status what contains the original exception message. - */ - @GET - @Produces({ MediaType.APPLICATION_JSON }) - @Path("/worklogs") - public Response findWorklogs( - @QueryParam("startDate") final String startDate, - @QueryParam("endDate") final String endDate, - @QueryParam("user") final String user, - @QueryParam("group") final String group, - @QueryParam("project") final String project, - @QueryParam("fields") final List fields) { - - Response checkRequiredFindWorklogsParamResponse = checkRequiredFindWorklogsParameter(startDate, user, group); - if (checkRequiredFindWorklogsParamResponse != null) { - return checkRequiredFindWorklogsParamResponse; - } - Calendar startDateCalendar; - try { - startDateCalendar = convertStartDate(startDate); - } catch (ParseException e) { - LOGGER.debug("Failed to convert start date", e); - return Response.status(Response.Status.BAD_REQUEST) - .entity("Cannot parse the 'startDate' parameter: " + startDate).build(); - } - Calendar endDateCalendar; - try { - endDateCalendar = convertEndDate(endDate); - } catch (ParseException e) { - LOGGER.debug("Failed to convert end date", e); - return Response.status(Response.Status.BAD_REQUEST) - .entity("Cannot parse the 'endDate' parameter: " + endDate).build(); - } - try { - return worklogQuery(startDateCalendar, endDateCalendar, user, group, project, fields, false); - } catch (Exception e) { - LOGGER.error("Failed to query the worklogs", e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(e.getMessage()).build(); - } + return searchResultsBean; + } + + private FieldJsonRepresentation getFieldValue(final FieldLayoutItem fieldLayoutItem, + final Issue issue) { + OrderableField field = fieldLayoutItem.getOrderableField(); + + if (field instanceof RestAwareField) { + RestAwareField restAware = (RestAwareField) field; + return restAware.getJsonFromIssue(issue, false, fieldLayoutItem); + } + else { + return null; + } + } + + /** + * Returns the selected issues based on the given JQL filter. + * + * @param jql + * JQL filter the search is based on. + * @return List of the matching JIRA Issues. + * @throws SearchException + * @throws JqlParseException + * Thrown when the given JQL is not valid. + */ + private List getIssuesByJQL(final String jql) + throws SearchException, + JqlParseException { + JiraAuthenticationContext authenticationContext = ComponentAccessor + .getJiraAuthenticationContext(); + User loggedInUser = authenticationContext.getLoggedInUser(); + + List issues = null; + SearchService searchService = ComponentAccessor.getComponentOfType(SearchService.class); + final ParseResult parseResult = searchService.parseQuery(loggedInUser, jql); + if (parseResult.isValid()) { + final SearchResults results = searchService.search(loggedInUser, + parseResult.getQuery(), PagerFilter.getUnlimitedFilter()); + issues = results.getIssues(); } + else { + throw new JqlParseException(null, parseResult.getErrors().toString()); + } + return issues; + } + + /** + * Check the given String is empty. + * + * @param theString + * The String variable. + * @return If the String is null or the String length equals whit 0 then true, else false. + */ + private boolean isStringEmpty(final String theString) { + if ((theString == null) || (theString.length() == 0)) { + return true; + } + return false; + } + + /** + * Summarize the worked time by issue. + * + * @param startDateCalendar + * The starting date of the search + * @param endDateCalendar + * The ending date of the search + * @param users + * The List of users whose worklog's are collected. + * @param issueIds + * A List of Issue IDs. Only these issues will be in the returned Map. + * @return A Map which keys are issueIDs and values are the worked time on that issue. + */ + private HashMap sumWorklogs(final Calendar startDateCalendar, + final Calendar endDateCalendar, + final List users, + final List issueIds) throws SQLException { + String schemaName = new DefaultOfBizConnectionFactory().getDatasourceInfo().getSchemaName(); + String worklogTablename = ""; + if ((schemaName != null) && !schemaName.equals("")) { + worklogTablename = schemaName + ".worklog"; + } else { + worklogTablename = "worklog"; + } + String query = "SELECT worklog.issueid, SUM(worklog.timeworked) AS timeworked" + + " FROM " + worklogTablename + + " WHERE worklog.startdate>=? AND worklog.startdate fields) - throws - URISyntaxException, SQLException { - - checkRequiredFindWorklogsByIssuesParameter(startDate, endDate, user, group); - - Calendar startDateCalendar = null; + StringBuilder authorPreparedParams = new StringBuilder(); + for (int i = 0; i < users.size(); i++) { + authorPreparedParams.append("?,"); + } + if (authorPreparedParams.length() > 0) { + authorPreparedParams.deleteCharAt(authorPreparedParams.length() - 1); + } + query += " AND worklog.author IN (" + authorPreparedParams.toString() + ")"; + query += " GROUP BY worklog.issueid"; + + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + HashMap result = new HashMap(); + try { + conn = new DefaultOfBizConnectionFactory().getConnection(); + ps = conn.prepareStatement(query); + int preparedIndex = 1; + ps.setTimestamp(preparedIndex++, new Timestamp(startDateCalendar.getTimeInMillis())); + ps.setTimestamp(preparedIndex++, new Timestamp(endDateCalendar.getTimeInMillis())); + for (String user : users) { + ps.setString(preparedIndex++, user); + } + + rs = ps.executeQuery(); + + while (rs.next()) + { + Long worklogIssueId = rs.getLong("issueid"); + if (issueIds.contains(worklogIssueId)) { + result.put(worklogIssueId, rs.getLong("timeworked")); + } + } + } finally { + if (rs != null) { try { - startDateCalendar = convertStartDate(startDate); - } catch (ParseException e) { - throw new RESTException(Response.Status.BAD_REQUEST, "Cannot parse the 'startDate' parameter: " + startDate); + rs.close(); + } catch (SQLException e) { + LOGGER.error("Cannot close ResultSet"); } - Calendar endDateCalendar = null; + } + if (ps != null) { try { - endDateCalendar = convertEndDate(endDate); - } catch (ParseException e) { - throw new RESTException(Response.Status.BAD_REQUEST, "Cannot parse the 'endDate' parameter: " + endDate); - } - if (startAt < 0) { - startAt = DEFAULT_STARTAT_PARAM; - } - if (maxResults < 0) { - maxResults = DEFAULT_MAXRESULT_PARAM; - } - List users = createUsers(user, group); - if (users.isEmpty()) { - throw new RESTException(Response.Status.BAD_REQUEST, - "Error running search: There is no group or user matching the given parameters."); - } - if (jql == null) { - jql = ""; + ps.close(); + } catch (SQLException e) { + LOGGER.error("Cannot close Statement"); } - List issues = null; + } + if (conn != null) { try { - issues = getIssuesByJQL(jql); - } catch (SearchException e) { - throw new RESTException(Response.Status.BAD_REQUEST, "Error running search: " + e); - } catch (JqlParseException e) { - throw new RESTException(Response.Status.BAD_REQUEST, e.getMessage()); + conn.close(); + } catch (SQLException e) { + LOGGER.error("Cannot close Connection"); } - - List issueIds = new ArrayList(); - for (Issue issue : issues) { - issueIds.add(issue.getId()); - } - Collections.reverse(issues); - - Map result = sumWorklogs(startDateCalendar, endDateCalendar, users, issueIds); - - IncludedFields includedFields = IncludedFields.includeNavigableByDefault(fields); - String baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL) - + "/rest/api/2/issue/"; - boolean isEmptyField = StringList.joinLists(fields).asList().contains("emptyFieldValue"); - List issueBeans = new ArrayList(); - for (int i = 0, j = 0; ((j < issues.size()) && (i < (startAt + maxResults)));) { - Issue issue = issues.get(j); - if (result.containsKey(issue.getId())) { - if ((i >= startAt)) { - IssueBeanWithTimespent bean = new IssueBeanWithTimespent(issue.getId(), issue.getKey(), - new URI(baseUrl + issue.getId()), result.get(issue.getId())); - bean.fieldsToInclude(includedFields); - if (!isEmptyField) { - addFields(issue, bean); - } - issueBeans.add(bean); - } - i++; - } - j++; - } - SearchResultsBeanWithTimespent searchResultsBean = new SearchResultsBeanWithTimespent(startAt, maxResults, - result.size(), issueBeans); - - return searchResultsBean; + } } - private FieldJsonRepresentation getFieldValue(final FieldLayoutItem fieldLayoutItem, final Issue issue) { - OrderableField field = fieldLayoutItem.getOrderableField(); - - if (field instanceof RestAwareField) { - RestAwareField restAware = (RestAwareField) field; - return restAware.getJsonFromIssue(issue, false, fieldLayoutItem); - } - else { - return null; - } + return result; + } + + /** + * The method to query worklogs. + * + * @param startDate + * The startDate calendar parameter. + * @param endDate + * The endDate calendar parameter. + * @param userString + * The user String parameter. + * @param groupString + * The group String parameter. + * @param projectString + * The project String parameter. + * @param updated + * True if the method give back the worklogs which were created or updated in the given + * period, else false. The false give back the worklogs of the period. + * @return JSONString what contains a list of queried worklogs. + * @throws ParseException + * If can't parse the dates. + * @throws GenericEntityException + * If the GenericDelegator throw a GenericEntityException. + * @throws JSONException + * If the createWorklogJSONObject method throw a JSONException. + */ + private Response worklogQuery(final Calendar startDate, final Calendar endDate, + final String userString, + final String groupString, final String projectString, final List fields, + final boolean updated) + throws DataAccessException, + SQLException, JSONException, ParseException { + + List worklogs = new ArrayList(); + + JiraAuthenticationContext authenticationContext = ComponentAccessor + .getJiraAuthenticationContext(); + User loggedInUser = authenticationContext.getLoggedInUser(); + List projects = createProjects(projectString, loggedInUser); + if ((projectString != null) && projects.isEmpty()) { + return Response + .status(Response.Status.BAD_REQUEST) + .entity( + "Error running search: There is no project matching the given 'project' parameter: " + + projectString).build(); + } + List users = createUsers(userString, groupString); + if (users.isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Error running search: There is no group or user matching the given parameters.") + .build(); + } + String schemaName = new DefaultOfBizConnectionFactory().getDatasourceInfo().getSchemaName(); + String worklogTablename = ""; + String issueTablename = ""; + if ((schemaName != null) && !schemaName.equals("")) { + worklogTablename = schemaName + ".worklog"; + issueTablename = schemaName + ".jiraissue"; + } else { + worklogTablename = "worklog"; + issueTablename = "jiraissue"; } - /** - * Returns the selected issues based on the given JQL filter. - * - * @param jql - * JQL filter the search is based on. - * @return List of the matching JIRA Issues. - * @throws SearchException - * @throws JqlParseException - * Thrown when the given JQL is not valid. - */ - private List getIssuesByJQL(final String jql) - throws SearchException, - JqlParseException { - JiraAuthenticationContext authenticationContext = ComponentAccessor.getJiraAuthenticationContext(); - User loggedInUser = authenticationContext.getLoggedInUser(); - - List issues = null; - SearchService searchService = ComponentAccessor.getComponentOfType(SearchService.class); - final ParseResult parseResult = searchService.parseQuery(loggedInUser, jql); - if (parseResult.isValid()) { - final SearchResults results = searchService.search(loggedInUser, - parseResult.getQuery(), PagerFilter.getUnlimitedFilter()); - issues = results.getIssues(); - } - else { - throw new JqlParseException(null, parseResult.getErrors().toString()); - } - return issues; - } - - /** - * Check the given String is empty. - * - * @param theString - * The String variable. - * @return If the String is null or the String length equals whit 0 then true, else false. - */ - private boolean isStringEmpty(final String theString) { - if ((theString == null) || (theString.length() == 0)) { - return true; - } - return false; - } - - /** - * Summarize the worked time by issue. - * - * @param startDateCalendar - * The starting date of the search - * @param endDateCalendar - * The ending date of the search - * @param users - * The List of users whose worklog's are collected. - * @param issueIds - * A List of Issue IDs. Only these issues will be in the returned Map. - * @return A Map which keys are issueIDs and values are the worked time on that issue. - */ - private HashMap sumWorklogs(final Calendar startDateCalendar, final Calendar endDateCalendar, - final List users, - final List issueIds) throws SQLException { - String schemaName = new DefaultOfBizConnectionFactory().getDatasourceInfo().getSchemaName(); - String worklogTablename = ""; - if ((schemaName != null) && !schemaName.equals("")) { - worklogTablename = schemaName + ".worklog"; - } else { - worklogTablename = "worklog"; - } - String query = "SELECT worklog.issueid, SUM(worklog.timeworked) AS timeworked" - + " FROM " + worklogTablename - + " WHERE worklog.startdate>=? AND worklog.startdate 0) { - authorPreparedParams.deleteCharAt(authorPreparedParams.length() - 1); - } - query += " AND worklog.author IN (" + authorPreparedParams.toString() + ")"; - query += " GROUP BY worklog.issueid"; - - Connection conn = new DefaultOfBizConnectionFactory().getConnection(); - PreparedStatement ps = null; + if (!projects.isEmpty() && !users.isEmpty()) { + + StringBuilder projectsPreparedParams = new StringBuilder(); + for (int i = 0; i < projects.size(); i++) { + projectsPreparedParams.append("?,"); + } + if (projectsPreparedParams.length() > 0) { + projectsPreparedParams.deleteCharAt(projectsPreparedParams.length() - 1); + } + StringBuilder usersPreparedParams = new StringBuilder(); + for (int i = 0; i < users.size(); i++) { + usersPreparedParams.append("?,"); + } + if (usersPreparedParams.length() > 0) { + usersPreparedParams.deleteCharAt(usersPreparedParams.length() - 1); + } + + String query = "SELECT worklog.id, worklog.startdate, worklog.issueid, worklog.author, worklog.timeworked, worklog.worklogbody, worklog.updated" + + " FROM " + worklogTablename + ", " + issueTablename + + " WHERE worklog.issueid=jiraissue.id" + + " AND worklog.startdate>=? AND worklog.startdate result = new HashMap(); + rs = ps.executeQuery(); while (rs.next()) { - Long worklogIssueId = rs.getLong("issueid"); - if (issueIds.contains(worklogIssueId)) { - result.put(worklogIssueId, rs.getLong("timeworked")); - } - } - rs.close(); - ps.close(); - conn.close(); - - return result; - } - - /** - * The method to query worklogs. - * - * @param startDate - * The startDate calendar parameter. - * @param endDate - * The endDate calendar parameter. - * @param userString - * The user String parameter. - * @param groupString - * The group String parameter. - * @param projectString - * The project String parameter. - * @param updated - * True if the method give back the worklogs which were created or updated in the given period, else - * false. The false give back the worklogs of the period. - * @return JSONString what contains a list of queried worklogs. - * @throws ParseException - * If can't parse the dates. - * @throws GenericEntityException - * If the GenericDelegator throw a GenericEntityException. - * @throws JSONException - * If the createWorklogJSONObject method throw a JSONException. - */ - private Response worklogQuery(final Calendar startDate, final Calendar endDate, final String userString, - final String groupString, final String projectString, final List fields, final boolean updated) - throws DataAccessException, - SQLException, JSONException, ParseException { - - List worklogs = new ArrayList(); - - JiraAuthenticationContext authenticationContext = ComponentAccessor.getJiraAuthenticationContext(); - User loggedInUser = authenticationContext.getLoggedInUser(); - List projects = createProjects(projectString, loggedInUser); - if ((projectString != null) && projects.isEmpty()) { - return Response - .status(Response.Status.BAD_REQUEST) - .entity("Error running search: There is no project matching the given 'project' parameter: " - + projectString).build(); + worklogs.add(createWorklogJSONObject(rs, fields)); } - List users = createUsers(userString, groupString); - if (users.isEmpty()) { - return Response.status(Response.Status.BAD_REQUEST) - .entity("Error running search: There is no group or user matching the given parameters.").build(); - } - String schemaName = new DefaultOfBizConnectionFactory().getDatasourceInfo().getSchemaName(); - String worklogTablename = ""; - String issueTablename = ""; - if ((schemaName != null) && !schemaName.equals("")) { - worklogTablename = schemaName + ".worklog"; - issueTablename = schemaName + ".jiraissue"; - } else { - worklogTablename = "worklog"; - issueTablename = "jiraissue"; - } - - if (!projects.isEmpty() && !users.isEmpty()) { - - StringBuilder projectsPreparedParams = new StringBuilder(); - for (int i = 0; i < projects.size(); i++) { - projectsPreparedParams.append("?,"); - } - if (projectsPreparedParams.length() > 0) { - projectsPreparedParams.deleteCharAt(projectsPreparedParams.length() - 1); - } - StringBuilder usersPreparedParams = new StringBuilder(); - for (int i = 0; i < users.size(); i++) { - usersPreparedParams.append("?,"); - } - if (usersPreparedParams.length() > 0) { - usersPreparedParams.deleteCharAt(usersPreparedParams.length() - 1); - } - - String query = "SELECT worklog.id, worklog.startdate, worklog.issueid, worklog.author, worklog.timeworked, worklog.worklogbody, worklog.updated" - + " FROM " + worklogTablename + ", " + issueTablename - + " WHERE worklog.issueid=jiraissue.id" - + " AND worklog.startdate>=? AND worklog.startdate() { - @Override - public int compare(final JSONObject o1, final JSONObject o2) { - long a = 0, b = 0; - try { - a = o1.getLong("id"); - b = o2.getLong("id"); - } catch (JSONException e) { - throw new RuntimeException(e); - } - return a > b ? +1 : a < b ? -1 : 0; - } - }); - JSONArray jsonArrayResult = new JSONArray(); - jsonArrayResult.put(worklogs); - return Response.ok(jsonArrayResult.toString()).build(); - - // 2.0.0 - // JSONObject jsonResult = new JSONObject(); - // jsonResult.put("worklogs", worklogs); - // return Response.ok(jsonResult.toString()).build(); + } } + + Collections.sort(worklogs, new Comparator() { + @Override + public int compare(final JSONObject o1, final JSONObject o2) { + long a = 0, b = 0; + try { + a = o1.getLong("id"); + b = o2.getLong("id"); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return a > b ? +1 : a < b ? -1 : 0; + } + }); + JSONArray jsonArrayResult = new JSONArray(); + jsonArrayResult.put(worklogs); + return Response.ok(jsonArrayResult.toString()).build(); + + // 2.0.0 + // JSONObject jsonResult = new JSONObject(); + // jsonResult.put("worklogs", worklogs); + // return Response.ok(jsonResult.toString()).build(); + } } diff --git a/core/src/main/resources/atlassian-plugin.xml b/core/src/main/resources/atlassian-plugin.xml index 8a3fdd3..51286f5 100644 --- a/core/src/main/resources/atlassian-plugin.xml +++ b/core/src/main/resources/atlassian-plugin.xml @@ -24,14 +24,14 @@ plugins-version="2"> - 1.2.0 + 1.2.1-SNAPSHOT Jira Worklog Query Plugin true - + Provides the REST resource for the Worklog Query plugin. diff --git a/itest/pom.xml b/itest/pom.xml index bc63cc2..23ce418 100644 --- a/itest/pom.xml +++ b/itest/pom.xml @@ -28,7 +28,7 @@ org.everit.jira.worklog.query.plugin - 1.2.0 + 1.2.1-SNAPSHOT org.everit.jira.worklog.query.plugin.parent diff --git a/itest/src/main/java/org/everit/jira/worklog/query/test/WorklogQueryTest.java b/itest/src/main/java/org/everit/jira/worklog/query/test/WorklogQueryTest.java index af39b8a..43ac613 100644 --- a/itest/src/main/java/org/everit/jira/worklog/query/test/WorklogQueryTest.java +++ b/itest/src/main/java/org/everit/jira/worklog/query/test/WorklogQueryTest.java @@ -1,134 +1,134 @@ -package org.everit.jira.worklog.query.test; - -/* - * Copyright (c) 2013, Everit Kft. - * - * All rights reserved. - * - * 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 3 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 - */ - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.core.util.Base64; - -/** - * The WorklogQueryTest class help test the plugin authorization and the plugin query. - */ -public final class WorklogQueryTest { - /** - * The logger used to log. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(WorklogQueryTest.class); - - /** - * The status code of the unsuccessful authorization. - */ - public static final int INVALID_AUTHOR_STATUS = 401; - /** - * The user name for authentication. - */ - public static final String USERNAME = "admin"; - /** - * The password for authentication. - */ - public static final String PASSWORD = "admin"; - - /** - * The WorklogQueryTest class main method. - * - * @param args - * The main args. - */ - public static void main(final String[] args) { - try { - WorklogQueryTest.simpleClientTest(); - WorklogQueryTest.simpleClientUpdateTest(); - } catch (Exception e) { - LOGGER.error("Fail to test jira-worklog-query", e); - } - } - - /** - * The jira-worklog-query HTTP BASIC AUTHORIZATION test. - * - * @throws Exception - * If any Exception happen. - */ - public static void simpleClientTest() throws Exception { - String url = - "http://localhost:8080/" - + "rest/jira-worklog-query/1.1.0/" - + "find/" - + "worklogs?startDate=2012-12-12&user=admin"; - LOGGER.info("Start the simple test"); - byte[] authByteArray = Base64.encode(USERNAME + ":" + PASSWORD); - String auth = new String(authByteArray, "UTF8"); - Client client = Client.create(); - WebResource webResource = client.resource(url); - ClientResponse response = webResource.header("Authorization", "Basic " + auth).type("application/json") - .accept("application/json").get(ClientResponse.class); - int statusCode = response.getStatus(); - - if (statusCode == INVALID_AUTHOR_STATUS) { - throw new Exception("Invalid Username or Password"); - } - final String stringResponse = response.getEntity(String.class); - LOGGER.info("sr: " + stringResponse); - - } - - /** - * The jira-worklog-query HTTP BASIC AUTHORIZATION test. - * - * @throws Exception - * If any Exception happen. - */ - public static void simpleClientUpdateTest() throws Exception { - String url = - "http://localhost:8080/" - + "rest/jira-worklog-query/1.1.0/" - + "find/" - + "updatedWorklogs?startDate=2013-04-15&user=admin"; - LOGGER.info("Start the simple test"); - byte[] authByteArray = Base64.encode(USERNAME + ":" + PASSWORD); - String auth = new String(authByteArray, "UTF8"); - Client client = Client.create(); - WebResource webResource = client.resource(url); - ClientResponse response = webResource.header("Authorization", "Basic " + auth).type("application/json") - .accept("application/json").get(ClientResponse.class); - int statusCode = response.getStatus(); - - if (statusCode == INVALID_AUTHOR_STATUS) { - throw new Exception("Invalid Username or Password"); - } - final String stringResponse = response.getEntity(String.class); - LOGGER.info("sr: " + stringResponse); - - } - - /** - * Simple private constructor. - */ - private WorklogQueryTest() { - - } - -} +package org.everit.jira.worklog.query.test; + +/* + * Copyright (c) 2013, Everit Kft. + * + * All rights reserved. + * + * 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 3 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 + */ + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.core.util.Base64; + +/** + * The WorklogQueryTest class help test the plugin authorization and the plugin query. + */ +public final class WorklogQueryTest { + /** + * The logger used to log. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(WorklogQueryTest.class); + + /** + * The status code of the unsuccessful authorization. + */ + public static final int INVALID_AUTHOR_STATUS = 401; + /** + * The user name for authentication. + */ + public static final String USERNAME = "admin"; + /** + * The password for authentication. + */ + public static final String PASSWORD = "admin"; + + /** + * The WorklogQueryTest class main method. + * + * @param args + * The main args. + */ + public static void main(final String[] args) { + try { + WorklogQueryTest.simpleClientTest(); + WorklogQueryTest.simpleClientUpdateTest(); + } catch (Exception e) { + LOGGER.error("Fail to test jira-worklog-query", e); + } + } + + /** + * The jira-worklog-query HTTP BASIC AUTHORIZATION test. + * + * @throws Exception + * If any Exception happen. + */ + public static void simpleClientTest() throws Exception { + String url = + "http://localhost:8080/" + + "rest/jira-worklog-query/1.1.0/" + + "find/" + + "worklogs?startDate=2012-12-12&user=admin"; + LOGGER.info("Start the simple test"); + byte[] authByteArray = Base64.encode(USERNAME + ":" + PASSWORD); + String auth = new String(authByteArray, "UTF8"); + Client client = Client.create(); + WebResource webResource = client.resource(url); + ClientResponse response = webResource.header("Authorization", "Basic " + auth).type("application/json") + .accept("application/json").get(ClientResponse.class); + int statusCode = response.getStatus(); + + if (statusCode == INVALID_AUTHOR_STATUS) { + throw new Exception("Invalid Username or Password"); + } + final String stringResponse = response.getEntity(String.class); + LOGGER.info("sr: " + stringResponse); + + } + + /** + * The jira-worklog-query HTTP BASIC AUTHORIZATION test. + * + * @throws Exception + * If any Exception happen. + */ + public static void simpleClientUpdateTest() throws Exception { + String url = + "http://localhost:8080/" + + "rest/jira-worklog-query/1.1.0/" + + "find/" + + "updatedWorklogs?startDate=2013-04-15&user=admin"; + LOGGER.info("Start the simple test"); + byte[] authByteArray = Base64.encode(USERNAME + ":" + PASSWORD); + String auth = new String(authByteArray, "UTF8"); + Client client = Client.create(); + WebResource webResource = client.resource(url); + ClientResponse response = webResource.header("Authorization", "Basic " + auth).type("application/json") + .accept("application/json").get(ClientResponse.class); + int statusCode = response.getStatus(); + + if (statusCode == INVALID_AUTHOR_STATUS) { + throw new Exception("Invalid Username or Password"); + } + final String stringResponse = response.getEntity(String.class); + LOGGER.info("sr: " + stringResponse); + + } + + /** + * Simple private constructor. + */ + private WorklogQueryTest() { + + } + +} diff --git a/pom.xml b/pom.xml index 6491160..48da730 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ org.everit.jira.worklog.query.plugin org.everit.jira.worklog.query.plugin.parent - 1.2.0 + 1.2.1-SNAPSHOT pom @@ -79,7 +79,7 @@ atlassian-public https://maven.atlassian.com/repository/public - + true never warn