From c0b9f1ae11aba61a2db26e56c768493bd7f9d7e7 Mon Sep 17 00:00:00 2001 From: Marius Merkevicius Date: Mon, 17 Oct 2016 00:57:23 +0300 Subject: [PATCH] Add worklog to issues --- src/main/java/net/rcarz/jiraclient/Issue.java | 35 +++++ .../java/net/rcarz/utils/WorklogUtils.java | 39 ++++++ .../rcarz/jiraclient/IssueWorklogTest.java | 130 ++++++++++++++++++ .../net/rcarz/jiraclient/WorklogTest.java | 9 +- .../utils/WorklogUtilsFormatDurationTest.java | 71 ++++++++++ 5 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 src/main/java/net/rcarz/utils/WorklogUtils.java create mode 100644 src/test/java/net/rcarz/jiraclient/IssueWorklogTest.java create mode 100644 src/test/java/net/rcarz/utils/WorklogUtilsFormatDurationTest.java diff --git a/src/main/java/net/rcarz/jiraclient/Issue.java b/src/main/java/net/rcarz/jiraclient/Issue.java index 1cb586a..e5f2d4c 100644 --- a/src/main/java/net/rcarz/jiraclient/Issue.java +++ b/src/main/java/net/rcarz/jiraclient/Issue.java @@ -23,11 +23,16 @@ import java.io.File; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; +import java.text.SimpleDateFormat; import java.util.*; +import net.rcarz.utils.WorklogUtils; import net.sf.json.JSON; import net.sf.json.JSONArray; import net.sf.json.JSONObject; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; /** * Represents a JIRA issue. @@ -1075,6 +1080,36 @@ public class Issue extends Resource { return new Comment(restclient, (JSONObject) result, key); } + /** + * Adds {@link WorkLog} to this issue + * @param comment provided comment + * @param startDate provided start date + * @param timeSpentSeconds provided time spent. This cannot be lower than 1m inute + * @return + * @throws JiraException when worklog creation fails + */ + public WorkLog addWorkLog(String comment, DateTime startDate, long timeSpentSeconds) throws JiraException { + try { + if (comment == null) + throw new IllegalArgumentException("Invalid comment."); + if (startDate == null) + throw new IllegalArgumentException("Invalid start time."); + if (timeSpentSeconds < 60) // We do not add a worklog that duration is below a minute + throw new IllegalArgumentException("Time spent cannot be lower than 1 minute."); + + JSONObject req = new JSONObject(); + req.put("comment", comment); + req.put("started", DateTimeFormat.forPattern(Field.DATETIME_FORMAT).print(startDate.getMillis())); + req.put("timeSpent", WorklogUtils.formatDurationFromSeconds(timeSpentSeconds)); + + JSON result = restclient.post(getRestUri(key) + "/worklog", req); + JSONObject jo = (JSONObject) result; + return new WorkLog(restclient, jo); + } catch (Exception ex) { + throw new JiraException("Failed add worklog to issue " + key, ex); + } + } + /** * Links this issue with another issue. * diff --git a/src/main/java/net/rcarz/utils/WorklogUtils.java b/src/main/java/net/rcarz/utils/WorklogUtils.java new file mode 100644 index 0000000..cf0d44e --- /dev/null +++ b/src/main/java/net/rcarz/utils/WorklogUtils.java @@ -0,0 +1,39 @@ +package net.rcarz.utils; + +import net.rcarz.jiraclient.WorkLog; +import org.joda.time.DurationFieldType; +import org.joda.time.Period; +import org.joda.time.PeriodType; + +/** + * Created by mariusmerkevicius on 1/30/16. + * A set of utils static methods help set {@link WorkLog} + */ +public class WorklogUtils { + + /** + * Formats duration time into pretty string format + * Does not output seconds + * @param durationInSeconds provided duration to format + * @return formatted duration + */ + public static String formatDurationFromSeconds(long durationInSeconds) { + if (durationInSeconds < 60) + return "0m"; + StringBuilder builder = new StringBuilder(); + PeriodType type = PeriodType.forFields(new DurationFieldType[]{ + DurationFieldType.hours(), + DurationFieldType.minutes() + }); + + Period period = new Period(1000 * durationInSeconds, type); + if (period.getHours() != 0) + builder.append(period.getHours()).append("h").append(" "); + if (period.getMinutes() != 0) + builder.append(period.getMinutes()).append("m").append(" "); + if ((builder.length() > 0) && builder.charAt(builder.length()-1) == " ".charAt(0)) + builder.deleteCharAt(builder.length()-1); + return builder.toString(); + } + +} diff --git a/src/test/java/net/rcarz/jiraclient/IssueWorklogTest.java b/src/test/java/net/rcarz/jiraclient/IssueWorklogTest.java new file mode 100644 index 0000000..03e1bd0 --- /dev/null +++ b/src/test/java/net/rcarz/jiraclient/IssueWorklogTest.java @@ -0,0 +1,130 @@ +package net.rcarz.jiraclient; + +import net.sf.json.JSON; +import net.sf.json.JSONObject; +import net.sf.json.JSONSerializer; +import org.joda.time.DateTime; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * Created by mariusmerkevicius on 1/30/16. + */ +public class IssueWorklogTest { + + @Test + public void testParsing_inputValid_shouldCreateJsonObject() throws Exception { + // Arrange + // Act + JSONObject worklogObject = (JSONObject) JSONSerializer.toJSON(RESPONSE_WORKLOG_BODY); + + // Assert + assertNotNull(worklogObject); + } + + @Test + public void testParsing_inputValidJson_shouldCreateWorklog() throws Exception { + // Arrange + // Act + WorkLog workLog = new WorkLog(mock(RestClient.class), (JSONObject) JSONSerializer.toJSON(RESPONSE_WORKLOG_BODY)); + + // Assert + assertThat(workLog).isNotNull(); + assertThat(workLog.getAuthor()).isNotNull(); + assertThat(workLog.getSelf()).isEqualTo("https://jira.test.lt/rest/api/2/issue/32374/worklog/80720"); + assertThat(workLog.getId()).isEqualTo("80720"); + assertThat(workLog.getComment()).isEqualTo("Test"); + assertThat(workLog.getCreatedDate().getTime()).isEqualTo(1454104800000L); + assertThat(workLog.getUpdatedDate().getTime()).isEqualTo(1454104800000L); + assertThat(workLog.getStarted().getTime()).isEqualTo(1453879853201L); + assertThat(workLog.getTimeSpent()).isEqualTo("5m"); + assertThat(workLog.getTimeSpentSeconds()).isEqualTo(300); + } + + @Test + public void testAdding_inputValid_shouldInvokeAdding() throws Exception { + // Arrange + Issue issue = mock(Issue.class); + issue.restclient = mock(RestClient.class); + doCallRealMethod().when(issue).addWorkLog(anyString(), any(DateTime.class), anyLong()); + + // Act + issue.addWorkLog("test", DateTime.now(), 60); + + // Assert + verify(issue.restclient).post(anyString(), any(JSON.class)); + } + + @Test + public void testAdding_inputNullComment_shouldNotAdd() throws Exception { + // Arrange + Issue issue = mock(Issue.class); + issue.restclient = mock(RestClient.class); + doCallRealMethod().when(issue).addWorkLog(anyString(), any(DateTime.class), anyLong()); + + // Act + try { + issue.addWorkLog(null, DateTime.now(), 120); + } catch (JiraException e) { + assertThat(e).hasMessageContaining("Failed add worklog to issue"); + } + + // Assert + verify(issue.restclient, never()).post(anyString(), any(JSON.class)); + } + + @Test + public void testAdding_inputNullDateTime_shouldNotAdd() throws Exception { + // Arrange + Issue issue = mock(Issue.class); + issue.restclient = mock(RestClient.class); + doCallRealMethod().when(issue).addWorkLog(anyString(), any(DateTime.class), anyLong()); + + // Act + try { + issue.addWorkLog("asdf", null, 120); + } catch (JiraException e) { + assertThat(e).hasMessageContaining("Failed add worklog to issue"); + } + + // Assert + verify(issue.restclient, never()).post(anyString(), any(JSON.class)); + } + + @Test + public void testAdding_inputDurationTooLow_shouldNotAdd() throws Exception { + // Arrange + Issue issue = mock(Issue.class); + issue.restclient = mock(RestClient.class); + doCallRealMethod().when(issue).addWorkLog(anyString(), any(DateTime.class), anyLong()); + + // Act + try { + issue.addWorkLog("asdf", DateTime.now(), 30); + } catch (JiraException e) { + assertThat(e).hasMessageContaining("Failed add worklog to issue"); + } + + // Assert + verify(issue.restclient, never()).post(anyString(), any(JSON.class)); + } + + + //region Constants + + // Mock response from jira + public static final String RESPONSE_WORKLOG_BODY = "{\"self\":\"https://jira.test.lt/rest/api/2/issue/32374/worklog/80720\",\"author\":{\"self\":\"https://jira.test.lt/rest/api/2/user?username=test%40test.lt\",\"name\":\"test@test.lt\",\"key\":\"test@test.lt\",\"emailAddress\":\"test@test.lt\",\"avatarUrls\":{\"48x48\":\"https://secure.gravatar.com/avatar/e4dacfe8f27cb89341bf990e556a4be0?d=mm&s=48\",\"24x24\":\"https://secure.gravatar.com/avatar/e4dacfe8f27cb89341bf990e556a4be0?d=mm&s=24\",\"16x16\":\"https://secure.gravatar.com/avatar/e4dacfe8f27cb89341bf990e556a4be0?d=mm&s=16\",\"32x32\":\"https://secure.gravatar.com/avatar/e4dacfe8f27cb89341bf990e556a4be0?d=mm&s=32\"},\"displayName\":\"Marius Merkevicius\",\"active\":true,\"timeZone\":\"Europe/Vilnius\"},\"updateAuthor\":{\"self\":\"https://jira.test.lt/rest/api/2/user?username=test%40test.lt\",\"name\":\"test@test.lt\",\"key\":\"test@test.lt\",\"emailAddress\":\"test@test.lt\",\"avatarUrls\":{\"48x48\":\"https://secure.gravatar.com/avatar/e4dacfe8f27cb89341bf990e556a4be0?d=mm&s=48\",\"24x24\":\"https://secure.gravatar.com/avatar/e4dacfe8f27cb89341bf990e556a4be0?d=mm&s=24\",\"16x16\":\"https://secure.gravatar.com/avatar/e4dacfe8f27cb89341bf990e556a4be0?d=mm&s=16\",\"32x32\":\"https://secure.gravatar.com/avatar/e4dacfe8f27cb89341bf990e556a4be0?d=mm&s=32\"},\"displayName\":\"Marius Merkevicius\",\"active\":true,\"timeZone\":\"Europe/Vilnius\"},\"comment\":\"Test\",\"created\":\"2016-01-30T20:46:16.583+0200\",\"updated\":\"2016-01-30T20:46:16.583+0200\",\"started\":\"2016-01-27T09:30:53.201+0200\",\"timeSpent\":\"5m\",\"timeSpentSeconds\":300,\"id\":\"80720\"}"; + + //endregion + + +} \ No newline at end of file diff --git a/src/test/java/net/rcarz/jiraclient/WorklogTest.java b/src/test/java/net/rcarz/jiraclient/WorklogTest.java index 2d6e12f..014fa57 100644 --- a/src/test/java/net/rcarz/jiraclient/WorklogTest.java +++ b/src/test/java/net/rcarz/jiraclient/WorklogTest.java @@ -59,11 +59,10 @@ public class WorklogTest { assertEquals("45517", workLog.getId()); String author = "joseph"; assertEquals(author, workLog.getAuthor().getName()); - 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()); + String started = "2015-08-17T12:19:00.000"; + assertEquals(started, simpleDateFormat.format(workLog.getStarted())); + String created = "2015-08-20T00:00:00.000"; + assertEquals(created, simpleDateFormat.format(workLog.getCreatedDate())); assertEquals(21600, workLog.getTimeSpentSeconds()); assertEquals(author, workLog.getUpdateAuthor().getName()); } diff --git a/src/test/java/net/rcarz/utils/WorklogUtilsFormatDurationTest.java b/src/test/java/net/rcarz/utils/WorklogUtilsFormatDurationTest.java new file mode 100644 index 0000000..d7ca723 --- /dev/null +++ b/src/test/java/net/rcarz/utils/WorklogUtilsFormatDurationTest.java @@ -0,0 +1,71 @@ +package net.rcarz.utils; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Created by mariusmerkevicius on 1/30/16. + */ +public class WorklogUtilsFormatDurationTest { + @Test + public void testEmpty() throws Exception { + assertEquals("0m", WorklogUtils.formatDurationFromSeconds(0)); + } + + @Test + public void testNegative() throws Exception { + assertEquals("0m", WorklogUtils.formatDurationFromSeconds(-200)); + } + + @Test public void testLowSecond() throws Exception { + assertEquals("0m", WorklogUtils.formatDurationFromSeconds(1)); + } + + @Test public void testSeconds() throws Exception { + assertEquals("0m", WorklogUtils.formatDurationFromSeconds(59)); + } + + @Test public void testMinutes() throws Exception { + assertEquals("1m", WorklogUtils.formatDurationFromSeconds(60)); + } + + @Test public void testMinutesAndSeconds() throws Exception { + assertEquals("1m", WorklogUtils.formatDurationFromSeconds( + 60 // 1 minute + + 2) // 2 seconds + ); + } + + @Test public void testMinutesAndSeconds2() throws Exception { + assertEquals("2m", WorklogUtils.formatDurationFromSeconds( + 60 // 1 minute + + 72) // 72 seconds + ); + } + + @Test public void testHours() throws Exception { + assertEquals("1h 10m", WorklogUtils.formatDurationFromSeconds( + (60 * 60) // 1 hour + + (10 * 60) // 10 minutes + + 3) // 3 seconds + ); + } + + @Test public void testDays() throws Exception { + assertEquals("50h 20m", WorklogUtils.formatDurationFromSeconds( + (60 * 60 * 50) // 50 hours + + (60 * 20) // 20 minutes + + (3) // s seconds + )); + } + + @Test public void testDays2() throws Exception { + assertEquals("50h 22m", WorklogUtils.formatDurationFromSeconds( + (60 * 60 * 50) // 50 hours + + (60 * 20) // 20 minutes + + (125) // 125 seconds + )); + } + +} \ No newline at end of file