#54 - Switch to verb-based issue update of Jira tickets.

Use PUT using update verbs instead of PUT'ing the whole Jira issue value object to prevent accidental update of unwanted but set fields.
This commit is contained in:
Mark Paluch
2017-06-07 13:57:02 +02:00
parent b8f15cebd4
commit 8b05b56cbb
4 changed files with 91 additions and 36 deletions

View File

@@ -309,12 +309,12 @@ class Jira implements JiraConnector {
return;
}
JiraIssue jiraIssue = JiraIssue.create().assignTo(jiraProperties.getCredentials());
JiraIssueUpdate editMeta = JiraIssueUpdate.create().assignTo(jiraProperties.getUsername());
logger.log("Ticket", "Self-assignment of %s", ticket);
try {
operations.exchange(ISSUE_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(jiraIssue, httpHeaders), String.class,
operations.exchange(ISSUE_TEMPLATE, HttpMethod.PUT, new HttpEntity<Object>(editMeta, httpHeaders), String.class,
parameters).getBody();
} catch (HttpClientErrorException e) {
logger.warn("Ticket", "Self-assignment of %s failed with status ", ticket, e.getStatusCode());

View File

@@ -86,29 +86,6 @@ class JiraIssue {
&& fields.hasSummary(Tracker.releaseTicketSummary(moduleIteration));
}
/**
* Assign to the user specified in {@link Credentials}.
*
* @param credentials must not be {@literal null}.
* @return
*/
public JiraIssue assignTo(Credentials credentials) {
return assignTo(credentials.getUsername());
}
/**
* Assign the ticket to {@code username}.
*
* @param username must not be empty and not {@literal null}.
* @return
*/
public JiraIssue assignTo(String username) {
Assert.hasText(username, "Username must not be empty!");
getFields().setAssignee(JiraUser.from(username));
return this;
}
/**
* Set the project to {@code projectKey}.
*

View File

@@ -0,0 +1,78 @@
package org.springframework.data.release.issues.jira;
import lombok.NonNull;
import lombok.Value;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* Value object to represent a Jira issue update via {@code PUT} with {@code update} fields.
*
* @author Mark Paluch
*/
@Value
class JiraIssueUpdate {
private final Map<String, Object> update;
private JiraIssueUpdate(Map<String, Object> update) {
this.update = update;
}
/**
* Create an empty {@link JiraIssueUpdate}.
*
* @return
*/
public static JiraIssueUpdate create() {
return new JiraIssueUpdate(Collections.emptyMap());
}
/**
* Assign the issue to {@code userId}.
*
* @param userId must not be {@literal null} or empty.
* @return
*/
public JiraIssueUpdate assignTo(String userId) {
Assert.hasText(userId, "UserId must not be null or empty!");
Map<String, Object> update = new LinkedHashMap<>(this.update);
update.put("assignee", new AssignTo(userId));
return new JiraIssueUpdate(update);
}
/**
* Serializes as [ {"set": "value"} ]
*/
@Value
@JsonSerialize(using = SetSerializer.class)
static class AssignTo {
final @NonNull String value;
}
static class SetSerializer extends JsonSerializer<AssignTo> {
@Override
public void serialize(AssignTo value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartArray();
gen.writeObject(Collections.singletonMap("set", Collections.singletonMap("name", value.getValue())));
gen.writeEndArray();
}
}
}

View File

@@ -54,12 +54,12 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule;
*/
public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
public static final String CREATE_ISSUE_URI = "/rest/api/2/issue";
public static final String CREATE_VERSION_URI = "/rest/api/2/version";
public static final String SEARCH_URI = "/rest/api/2/search";
public static final String PROJECT_VERSION_URI = "/rest/api/2/project/%s/version";
public static final String PROJECT_COMPONENTS_URI = "/rest/api/2/project/%s/components";
public static final ModuleIteration REST_HOPPER_RC1 = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "REST");
static final String CREATE_ISSUE_URI = "/rest/api/2/issue";
static final String CREATE_VERSION_URI = "/rest/api/2/version";
static final String SEARCH_URI = "/rest/api/2/search";
static final String PROJECT_VERSION_URI = "/rest/api/2/project/%s/version";
static final String PROJECT_COMPONENTS_URI = "/rest/api/2/project/%s/components";
static final ModuleIteration REST_HOPPER_RC1 = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "REST");
@Rule public WireMockRule mockService = new WireMockRule(
wireMockConfig().port(8888).fileSource(new ClasspathFileSource("integration/jira")));
@@ -265,7 +265,7 @@ public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
}
/**
* @see #5
* @see #5, #54
*/
@Test
public void assignTicketToMe() {
@@ -273,13 +273,13 @@ public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
mockService.stubFor(get(urlPathMatching("/rest/api/2/issue/DATAREDIS-302")).//
willReturn(json("existingTicket.json")));
mockService.stubFor(post(urlPathMatching("/rest/api/2/issue/DATAREDIS-302")).//
mockService.stubFor(put(urlPathMatching("/rest/api/2/issue/DATAREDIS-302")).//
willReturn(aResponse().withStatus(204)));
jira.assignTicketToMe(new Ticket("DATAREDIS-99999", "", null));
jira.assignTicketToMe(new Ticket("DATAREDIS-302", "", null));
verify(postRequestedFor(urlPathMatching("/rest/api/2/issue/DATAREDIS-302"))
.withRequestBody(equalToJson("{\"fields\":{\"assignee\":{\"name\":\"dummy\"}}}")));
verify(putRequestedFor(urlPathMatching("/rest/api/2/issue/DATAREDIS-302"))
.withRequestBody(equalToJson("{\"update\":{\"assignee\":[ {\"set\":{\"name\":\"dummy\"}} ] }}")));
}
/**