#53 - Skip Jira ticket assignment if ticket is already assigned.
Jira tickets are no longer self-assigned if they are assigned already.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-release-cli</artifactId>
|
||||
@@ -115,6 +115,12 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -44,8 +44,11 @@ import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
@@ -58,7 +61,7 @@ class Jira implements JiraConnector {
|
||||
|
||||
private static final String BASE_URI = "{jiraBaseUrl}/rest/api/2";
|
||||
private static final String CREATE_ISSUES_TEMPLATE = BASE_URI + "/issue";
|
||||
private static final String UPDATE_ISSUE_TEMPLATE = BASE_URI + "/issue/{ticketId}";
|
||||
private static final String ISSUE_TEMPLATE = BASE_URI + "/issue/{ticketId}";
|
||||
private static final String PROJECT_VERSIONS_TEMPLATE = BASE_URI + "/project/{project}/version?startAt={startAt}";
|
||||
private static final String PROJECT_COMPONENTS_TEMPLATE = BASE_URI + "/project/{project}/components";
|
||||
private static final String VERSIONS_TEMPLATE = BASE_URI + "/version";
|
||||
@@ -298,13 +301,27 @@ class Jira implements JiraConnector {
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("ticketId", ticket.getId());
|
||||
|
||||
JiraIssue currentIssue = getJiraIssue(ticket.getId())
|
||||
.orElseThrow(() -> new IllegalStateException(String.format("Ticket %s does not exist", ticket.getId())));
|
||||
|
||||
if (currentIssue.isAssignedTo(jiraProperties.getUsername())) {
|
||||
logger.log("Ticket", "Skipping self-assignment of %s", ticket);
|
||||
return;
|
||||
}
|
||||
|
||||
JiraIssue jiraIssue = JiraIssue.create().assignTo(jiraProperties.getCredentials());
|
||||
|
||||
operations.exchange(UPDATE_ISSUE_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(jiraIssue, httpHeaders),
|
||||
String.class, parameters).getBody();
|
||||
logger.log("Ticket", "Self-assignment of %s", ticket);
|
||||
|
||||
try {
|
||||
operations.exchange(ISSUE_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(jiraIssue, httpHeaders), String.class,
|
||||
parameters).getBody();
|
||||
} catch (HttpClientErrorException e) {
|
||||
logger.warn("Ticket", "Self-assignment of %s failed with status ", ticket, e.getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.IssueTracker#assignReleaseTicketToMe(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@@ -444,6 +461,24 @@ class Jira implements JiraConnector {
|
||||
return jiraIssue;
|
||||
}
|
||||
|
||||
private Optional<JiraIssue> getJiraIssue(String ticketId) {
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("ticketId", ticketId);
|
||||
|
||||
try {
|
||||
ResponseEntity<JiraIssue> jiraIssue = operations.getForEntity(ISSUE_TEMPLATE, JiraIssue.class, parameters);
|
||||
|
||||
return Optional.of(jiraIssue.getBody());
|
||||
} catch (HttpClientErrorException e) {
|
||||
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private JiraIssues execute(String context, JqlQuery query, HttpHeaders headers, JiraIssuesCallback callback) {
|
||||
|
||||
JiraIssues issues;
|
||||
@@ -530,7 +565,7 @@ class Jira implements JiraConnector {
|
||||
|
||||
/**
|
||||
* Returns new {@link HttpHeaders} with authentication headers.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private HttpHeaders newUserScopedHttpHeaders() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,7 +42,7 @@ class JiraIssue {
|
||||
|
||||
/**
|
||||
* Creates a new Jira issue value object.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static JiraIssue create() {
|
||||
@@ -51,7 +51,7 @@ class JiraIssue {
|
||||
|
||||
/**
|
||||
* Creates a new Jira issue value object with a issue type of {@link IssueType#TASK}.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static JiraIssue createTask() {
|
||||
@@ -98,7 +98,7 @@ class JiraIssue {
|
||||
|
||||
/**
|
||||
* Assign the ticket to {@code username}.
|
||||
*
|
||||
*
|
||||
* @param username must not be empty and not {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@@ -111,7 +111,7 @@ class JiraIssue {
|
||||
|
||||
/**
|
||||
* Set the project to {@code projectKey}.
|
||||
*
|
||||
*
|
||||
* @param projectKey must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@@ -123,7 +123,7 @@ class JiraIssue {
|
||||
|
||||
/**
|
||||
* Set the issue summary.
|
||||
*
|
||||
*
|
||||
* @param summary must not be empty and not {@literal null}.
|
||||
*/
|
||||
public JiraIssue summary(String summary) {
|
||||
@@ -135,7 +135,7 @@ class JiraIssue {
|
||||
|
||||
/**
|
||||
* Set the fix version.
|
||||
*
|
||||
*
|
||||
* @param moduleIteration must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@@ -147,6 +147,10 @@ class JiraIssue {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isAssignedTo(String username) {
|
||||
return getFields().getAssignee() != null && getFields().getAssignee().matches(username);
|
||||
}
|
||||
|
||||
private static boolean wasBackportedFrom(Train train, List<FixVersion> fixVersions) {
|
||||
return fixVersions.size() > 1 && isPresent(train, fixVersions);
|
||||
}
|
||||
@@ -252,5 +256,9 @@ class JiraIssue {
|
||||
static JiraUser from(String username) {
|
||||
return new JiraUser(username, null);
|
||||
}
|
||||
|
||||
public boolean matches(String username) {
|
||||
return username.equalsIgnoreCase(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
public class Logger {
|
||||
@@ -51,4 +52,8 @@ public class Logger {
|
||||
public void log(String context, Object template, Object... args) {
|
||||
LOGGER.info(String.format(PREFIX_TEMPLATE, context, String.format(template.toString(), args)));
|
||||
}
|
||||
|
||||
public void warn(String context, Object template, Object... args) {
|
||||
LOGGER.warning(String.format(PREFIX_TEMPLATE, context, String.format(template.toString(), args)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.release.issues.jira;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
@@ -34,10 +33,6 @@ import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.release.AbstractIntegrationTests;
|
||||
import org.springframework.data.release.issues.Ticket;
|
||||
import org.springframework.data.release.issues.jira.Jira;
|
||||
import org.springframework.data.release.issues.jira.JiraConnector;
|
||||
import org.springframework.data.release.issues.jira.JiraProperties;
|
||||
import org.springframework.data.release.issues.jira.JiraReleaseVersion;
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.ProjectKey;
|
||||
@@ -54,7 +49,7 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
|
||||
/**
|
||||
* Integration Tests for {@link Jira} using a local {@link WireMockRule} server.
|
||||
*
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
|
||||
@@ -80,6 +75,7 @@ public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(properties.getApiUrl()).build();
|
||||
Assume.assumeThat(uriComponents.getHost(), is("localhost"));
|
||||
|
||||
properties.setUsername("dummy");
|
||||
jira.reset();
|
||||
}
|
||||
|
||||
@@ -209,7 +205,6 @@ public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
|
||||
+ "\"issuetype\":{\"name\":\"Task\"},\"summary\":\"Release 2.5 RC1 (Hopper)\","
|
||||
+ "\"fixVersions\":[{\"name\":\"2.5 RC1 (Hopper)\"}],"
|
||||
+ "\"components\":[{\"name\":\"Infrastructure\"}]}}")));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +226,6 @@ public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
|
||||
.withRequestBody(equalToJson("{\"fields\":{\"project\":{\"key\":\"DATAREST\"},"
|
||||
+ "\"issuetype\":{\"name\":\"Task\"},\"summary\":\"Release 2.5 RC1 (Hopper)\","
|
||||
+ "\"fixVersions\":[{\"name\":\"2.5 RC1 (Hopper)\"}]}}")));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,17 +268,39 @@ public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
|
||||
* @see #5
|
||||
*/
|
||||
@Test
|
||||
public void assignTicketToMe() throws Exception {
|
||||
public void assignTicketToMe() {
|
||||
|
||||
mockService.stubFor(post(urlPathMatching("/rest/api/2/issue/DATAREDIS-99999")).//
|
||||
mockService.stubFor(get(urlPathMatching("/rest/api/2/issue/DATAREDIS-302")).//
|
||||
willReturn(json("existingTicket.json")));
|
||||
|
||||
mockService.stubFor(post(urlPathMatching("/rest/api/2/issue/DATAREDIS-302")).//
|
||||
willReturn(aResponse().withStatus(204)));
|
||||
|
||||
jira.assignTicketToMe(new Ticket("DATAREDIS-99999", "", null));
|
||||
|
||||
verify(postRequestedFor(urlPathMatching("/rest/api/2/issue/DATAREDIS-99999"))
|
||||
verify(postRequestedFor(urlPathMatching("/rest/api/2/issue/DATAREDIS-302"))
|
||||
.withRequestBody(equalToJson("{\"fields\":{\"assignee\":{\"name\":\"dummy\"}}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #5, #53
|
||||
*/
|
||||
@Test
|
||||
public void skipTicketAssignmentIfAssigned() {
|
||||
|
||||
properties.setUsername("mp911de");
|
||||
|
||||
mockService.stubFor(get(urlPathMatching("/rest/api/2/issue/DATACASS-302")).//
|
||||
willReturn(json("existingTicket.json")));
|
||||
|
||||
mockService.stubFor(post(urlPathMatching("/rest/api/2/issue/DATACASS-302")).//
|
||||
willReturn(aResponse().withStatus(204)));
|
||||
|
||||
jira.assignTicketToMe(new Ticket("DATACASS-302", "", null));
|
||||
|
||||
verify(0, postRequestedFor(urlPathMatching("/rest/api/2/issue/DATACASS-302")));
|
||||
}
|
||||
|
||||
private void mockSearchWith(String fromClassPath) {
|
||||
mockService.stubFor(get(urlPathMatching(SEARCH_URI)).//
|
||||
willReturn(json(fromClassPath)));
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"expand": "renderedFields,names,schema,transitions,operations,editmeta,changelog",
|
||||
"id": "68967",
|
||||
"self": "https://jira.spring.io/rest/api/2/issue/68967",
|
||||
"key": "DATACASS-302",
|
||||
"fields": {
|
||||
"issuetype": {
|
||||
"self": "https://jira.spring.io/rest/api/2/issuetype/3",
|
||||
"id": "3",
|
||||
"description": "A task that needs to be done.",
|
||||
"iconUrl": "https://jira.spring.io/images/icons/issuetypes/task.png",
|
||||
"name": "Task",
|
||||
"subtask": false
|
||||
},
|
||||
"timespent": null,
|
||||
"customfield_10150": null,
|
||||
"project": {
|
||||
"self": "https://jira.spring.io/rest/api/2/project/11403",
|
||||
"id": "11403",
|
||||
"key": "DATACASS",
|
||||
"name": "Spring Data for Apache Cassandra",
|
||||
"avatarUrls": {
|
||||
"48x48": "https://jira.spring.io/secure/projectavatar?pid=11403&avatarId=12504",
|
||||
"24x24": "https://jira.spring.io/secure/projectavatar?size=small&pid=11403&avatarId=12504",
|
||||
"16x16": "https://jira.spring.io/secure/projectavatar?size=xsmall&pid=11403&avatarId=12504",
|
||||
"32x32": "https://jira.spring.io/secure/projectavatar?size=medium&pid=11403&avatarId=12504"
|
||||
},
|
||||
"projectCategory": {
|
||||
"self": "https://jira.spring.io/rest/api/2/projectCategory/10000",
|
||||
"id": "10000",
|
||||
"description": "Top-level Spring projects",
|
||||
"name": "Spring Projects"
|
||||
}
|
||||
},
|
||||
"fixVersions": [],
|
||||
"aggregatetimespent": null,
|
||||
"resolution": null,
|
||||
"resolutiondate": null,
|
||||
"workratio": -1,
|
||||
"lastViewed": null,
|
||||
"watches": {
|
||||
"self": "https://jira.spring.io/rest/api/2/issue/DATACASS-302/watchers",
|
||||
"watchCount": 1,
|
||||
"isWatching": true
|
||||
},
|
||||
"customfield_10180": "27907200",
|
||||
"customfield_10181": "true",
|
||||
"customfield_10380": "9223372036854775807",
|
||||
"customfield_10182": "mp911de(mp911de)",
|
||||
"created": "2016-06-20T11:54:21.542+0000",
|
||||
"customfield_10381": "9223372036854775807",
|
||||
"customfield_10140": null,
|
||||
"customfield_10580": null,
|
||||
"customfield_10142": null,
|
||||
"priority": {
|
||||
"self": "https://jira.spring.io/rest/api/2/priority/4",
|
||||
"iconUrl": "https://jira.spring.io/images/icons/priorities/minor.png",
|
||||
"name": "Minor",
|
||||
"id": "4"
|
||||
},
|
||||
"customfield_10981": null,
|
||||
"labels": [],
|
||||
"timeestimate": null,
|
||||
"aggregatetimeoriginalestimate": null,
|
||||
"versions": [],
|
||||
"issuelinks": [],
|
||||
"assignee": {
|
||||
"self": "https://jira.spring.io/rest/api/2/user?username=mp911de",
|
||||
"name": "mp911de",
|
||||
"key": "mp911de",
|
||||
"emailAddress": "mpaluch at pivotal dot io",
|
||||
"avatarUrls": {
|
||||
"48x48": "https://jira.spring.io/secure/useravatar?ownerId=mp911de&avatarId=14015",
|
||||
"24x24": "https://jira.spring.io/secure/useravatar?size=small&ownerId=mp911de&avatarId=14015",
|
||||
"16x16": "https://jira.spring.io/secure/useravatar?size=xsmall&ownerId=mp911de&avatarId=14015",
|
||||
"32x32": "https://jira.spring.io/secure/useravatar?size=medium&ownerId=mp911de&avatarId=14015"
|
||||
},
|
||||
"displayName": "Mark Paluch",
|
||||
"active": true,
|
||||
"timeZone": "Europe/Berlin"
|
||||
},
|
||||
"updated": "2016-06-20T11:54:42.720+0000",
|
||||
"status": {
|
||||
"self": "https://jira.spring.io/rest/api/2/status/1",
|
||||
"description": "The issue is open and ready for the assignee to start work on it.",
|
||||
"iconUrl": "https://jira.spring.io/images/icons/statuses/open.png",
|
||||
"name": "Open",
|
||||
"id": "1",
|
||||
"statusCategory": {
|
||||
"self": "https://jira.spring.io/rest/api/2/statuscategory/2",
|
||||
"id": 2,
|
||||
"key": "new",
|
||||
"colorName": "blue-gray",
|
||||
"name": "To Do"
|
||||
}
|
||||
},
|
||||
"components": [],
|
||||
"customfield_10170": null,
|
||||
"customfield_10171": null,
|
||||
"timeoriginalestimate": null,
|
||||
"description": "Add support for Cassandra {{time}} data type.\r\n\r\n{{time}} represents a Time string, such as 13:30:54.234 but it's mapped to the {{long}} Java type. The {{long}} value denotes millis since midnight. Updates require quoting of the value:\r\n\r\n{code}\r\nUPDATE mytable SET mytimefield ='123456';\r\n{code}\r\n\r\nPlease see also https://issues.apache.org/jira/browse/CASSANDRA-11798",
|
||||
"timetracking": {},
|
||||
"attachment": [],
|
||||
"aggregatetimeestimate": null,
|
||||
"summary": "Support Cassandra time columns",
|
||||
"creator": {
|
||||
"self": "https://jira.spring.io/rest/api/2/user?username=mp911de",
|
||||
"name": "mp911de",
|
||||
"key": "mp911de",
|
||||
"emailAddress": "mpaluch at pivotal dot io",
|
||||
"avatarUrls": {
|
||||
"48x48": "https://jira.spring.io/secure/useravatar?ownerId=mp911de&avatarId=14015",
|
||||
"24x24": "https://jira.spring.io/secure/useravatar?size=small&ownerId=mp911de&avatarId=14015",
|
||||
"16x16": "https://jira.spring.io/secure/useravatar?size=xsmall&ownerId=mp911de&avatarId=14015",
|
||||
"32x32": "https://jira.spring.io/secure/useravatar?size=medium&ownerId=mp911de&avatarId=14015"
|
||||
},
|
||||
"displayName": "Mark Paluch",
|
||||
"active": true,
|
||||
"timeZone": "Europe/Berlin"
|
||||
},
|
||||
"customfield_10280": "9223372036854775807",
|
||||
"subtasks": [],
|
||||
"customfield_10480": null,
|
||||
"customfield_10680": null,
|
||||
"customfield_10120": null,
|
||||
"reporter": {
|
||||
"self": "https://jira.spring.io/rest/api/2/user?username=mp911de",
|
||||
"name": "mp911de",
|
||||
"key": "mp911de",
|
||||
"emailAddress": "mpaluch at pivotal dot io",
|
||||
"avatarUrls": {
|
||||
"48x48": "https://jira.spring.io/secure/useravatar?ownerId=mp911de&avatarId=14015",
|
||||
"24x24": "https://jira.spring.io/secure/useravatar?size=small&ownerId=mp911de&avatarId=14015",
|
||||
"16x16": "https://jira.spring.io/secure/useravatar?size=xsmall&ownerId=mp911de&avatarId=14015",
|
||||
"32x32": "https://jira.spring.io/secure/useravatar?size=medium&ownerId=mp911de&avatarId=14015"
|
||||
},
|
||||
"displayName": "Mark Paluch",
|
||||
"active": true,
|
||||
"timeZone": "Europe/Berlin"
|
||||
},
|
||||
"customfield_10000": null,
|
||||
"customfield_10880": "0|i09a87:",
|
||||
"aggregateprogress": {
|
||||
"progress": 0,
|
||||
"total": 0
|
||||
},
|
||||
"customfield_10002": null,
|
||||
"customfield_10684": null,
|
||||
"environment": null,
|
||||
"duedate": null,
|
||||
"progress": {
|
||||
"progress": 0,
|
||||
"total": 0
|
||||
},
|
||||
"comment": {
|
||||
"startAt": 0,
|
||||
"maxResults": 0,
|
||||
"total": 0,
|
||||
"comments": []
|
||||
},
|
||||
"votes": {
|
||||
"self": "https://jira.spring.io/rest/api/2/issue/DATACASS-302/votes",
|
||||
"votes": 0,
|
||||
"hasVoted": false
|
||||
},
|
||||
"worklog": {
|
||||
"startAt": 0,
|
||||
"maxResults": 20,
|
||||
"total": 0,
|
||||
"worklogs": []
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user