Skip to content

Commit

Permalink
Adjust the handling of the issues deleted upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
marko-bekhta committed Oct 28, 2024
1 parent 8211ca8 commit 80238a6
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 23 deletions.
16 changes: 15 additions & 1 deletion src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ interface JiraProjectGroup {
* review your project scheme to see which transitions can be used to set the
* desired status.
*/
ValueMapping statuses();
StatusesValueMapping statuses();

/**
* Mapping of upstream issue types to downstream ones. Please make sure to
Expand Down Expand Up @@ -263,6 +263,20 @@ interface IssueTypeValueMapping extends ValueMapping {
Optional<String> epicLinkDestinationLabelCustomFieldName();
}

interface StatusesValueMapping extends ValueMapping {
/**
* @return The name of the resolution to apply to the "Close" transition when
* closing the issue deleted upstream before archiving it.
*/
Optional<String> deletedResolution();

/**
* @return The id of the transition to apply to get the "Close" transition when
* closing the issue deleted upstream before archiving it.
*/
Optional<String> deletedTransition();
}

interface UserValueMapping extends ValueMapping {
/**
* @return the name of the property to apply the assignee value to. With Jira
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.hibernate.infra.replicate.jira.JiraConfig;
import org.hibernate.infra.replicate.jira.service.jira.client.JiraRestClient;
import org.hibernate.infra.replicate.jira.service.jira.client.JiraRestClientBuilder;
import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueDeleteEventHandler;
import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueSimpleUpsertEventHandler;
import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueTransitionOnlyEventHandler;
import org.hibernate.infra.replicate.jira.service.jira.model.hook.JiraWebHookEvent;
Expand Down Expand Up @@ -157,6 +158,22 @@ public void registerManagementRoutes(@Observes ManagementInterface mi) {
triggerSyncEvent(context.sourceJiraClient().getIssue(issue), context);
rc.end();
});
mi.router().get("/sync/issues/deleted/:project").blockingHandler(rc -> {
String project = rc.pathParam("project");
String issues = rc.queryParam("issues").getFirst();

HandlerProjectContext context = contextPerProject.get(project);

if (context == null) {
throw new IllegalArgumentException("Unknown project '%s'".formatted(project));
}

String[] split = issues.split(",");
for (String key : split) {
context.submitTask(new JiraIssueDeleteEventHandler(reportingConfig, context, -1L, key));
}
rc.end();
});
mi.router().get("/sync/issues/transition/re-sync/:project").blockingHandler(rc -> {
// TODO: we can remove this one once we figure out why POST management does not
// work correctly...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ JiraIssues find(@QueryParam("jql") String query, @QueryParam("startAt") int star
@Path("/issue/{issueKey}/transitions")
void transition(@PathParam("issueKey") String issueKey, JiraTransition transition);

@PUT
@Path("/issue/{issueKey}/archive")
void archive(@PathParam("issueKey") String issueKey);

@ClientObjectMapper
static ObjectMapper objectMapper(ObjectMapper defaultObjectMapper) {
return defaultObjectMapper.copy().setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ public void transition(String issueKey, JiraTransition transition) {
withRetry(() -> delegate.transition(issueKey, transition));
}

@Override
public void archive(String issueKey) {
withRetry(() -> delegate.archive(issueKey));
}

private static final int RETRIES = 5;
private static final Duration WAIT_BETWEEN_RETRIES = Duration.of(2, ChronoUnit.SECONDS);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package org.hibernate.infra.replicate.jira.service.jira.handler;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.hibernate.infra.replicate.jira.service.jira.HandlerProjectContext;
import org.hibernate.infra.replicate.jira.service.jira.client.JiraRestException;
import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraFields;
import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssue;
import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssueTransition;
import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransition;
import org.hibernate.infra.replicate.jira.service.reporting.ReportingConfig;

public class JiraIssueDeleteEventHandler extends JiraEventHandler {
Expand All @@ -21,45 +27,75 @@ public JiraIssueDeleteEventHandler(ReportingConfig reportingConfig, HandlerProje
@Override
protected void doRun() {
// TODO: do we actually want to delete the issue ? or maybe let's instead add a
// label,
// and update the summary, saying that the issue is deleted:
// label, and update the summary, saying that the issue is deleted:

// first let's make sure that the issue is actually deleted upstream:
try {
// Note: we do the search based on the jira key as we want to make sure
// that such key does not exist, searching by ID may also not find the issue,
// but then if the issue is not there we cannot check that the key matches the
// ID.
context.sourceJiraClient().getIssue(key);
JiraIssue issue = context.sourceJiraClient().getIssue(key);
if (issue != null && !key.equals(issue.key)) {
// means the issue got moved:
handleDeletedMovedIssue("MOVED (to %s)".formatted(issue.key));
return;
}

// if the issue is deleted then we should get a 404 and never reach this line:
failureCollector.critical("Request to delete an issue %s that is actually not deleted".formatted(key));
return;
} catch (JiraRestException e) {
// all good the issue is not available let's mark the other one as deleted now:
handleDeletedMovedIssue("DELETED");
}
}

try {
String destinationKey = toDestinationKey(key);
JiraIssue issue = context.destinationJiraClient().getIssue(destinationKey);
JiraIssue updated = new JiraIssue();

updated.fields = new JiraFields();
updated.fields.summary = "DELETED upstream: " + issue.fields.summary;
if (issue.fields.labels == null) {
issue.fields.labels = List.of();
}
ArrayList<String> updatedLabels = new ArrayList<>(issue.fields.labels);
updatedLabels.add("Deleted Upstream");
updated.fields.labels = updatedLabels;

context.destinationJiraClient().update(destinationKey, updated);
} catch (Exception ex) {
failureCollector.critical(
"Unable to mark the issue %s as deleted: %s".formatted(objectId, ex.getMessage()), ex);
private void handleDeletedMovedIssue(String type) {
try {
String destinationKey = toDestinationKey(key);
JiraIssue issue = context.destinationJiraClient().getIssue(destinationKey);
JiraIssue updated = new JiraIssue();

updated.fields = new JiraFields();
updated.fields.summary = "%s upstream: %s".formatted(type, issue.fields.summary);
if (issue.fields.labels == null) {
issue.fields.labels = List.of();
}
Set<String> updatedLabels = new HashSet<>(issue.fields.labels);
updatedLabels.add("deleted_upstream");
updated.fields.labels = new ArrayList<>(updatedLabels);
updated.fields.priority = null;
updated.fields.issuetype = null;
updated.fields.project = null;

context.destinationJiraClient().update(destinationKey, updated);

prepareTransition()
.ifPresent(transition -> context.destinationJiraClient().transition(destinationKey, transition));

context.destinationJiraClient().archive(destinationKey);
} catch (Exception ex) {
failureCollector.critical("Unable to mark the issue %s as deleted: %s".formatted(objectId, ex.getMessage()),
ex);
}
}

private Optional<JiraTransition> prepareTransition() {
Optional<String> deletedTransition = context.projectGroup().statuses().deletedTransition();
if (deletedTransition.isPresent()) {
JiraTransition transition = new JiraTransition();
transition.transition = new JiraIssueTransition(deletedTransition.get());

Optional<String> deletedResolution = context.projectGroup().statuses().deletedResolution();
deletedResolution.ifPresent(
name -> transition.properties().put("fields", Map.of("resolution", Map.of("name", name))));

return Optional.of(transition);
}

return Optional.empty();
}

@Override
public String toString() {
return "JiraIssueDeleteEventHandler[" + "key='" + key + '\'' + ", objectId=" + objectId + ", project="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ public void transition(String issueKey, JiraTransition transition) {
// do nothing
}

@Override
public void archive(String issueKey) {
// do nothing
}

private JiraIssueLink sampleIssueLink(Long id) {
try {
return objectMapper.readValue("""
Expand Down

0 comments on commit 80238a6

Please sign in to comment.