diff --git a/src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java b/src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java index 4aa9cfa..0435f01 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java @@ -58,6 +58,12 @@ interface JiraProjectGroup { */ StatusesValueMapping statuses(); + /** + * Mapping of upstream resolutions to downstream ones. Please make sure to + * review your project scheme to see which resolutions can be used. + */ + ResolutionValueMapping resolutions(); + /** * Mapping of upstream issue types to downstream ones. Please make sure to * review your project scheme to see which issue types are available. @@ -347,6 +353,15 @@ interface StatusesValueMapping extends ValueMapping { Map> ignoreTransitionCondition(); } + interface ResolutionValueMapping extends ValueMapping { + /** + * @return The name of the resolution to apply for the transition applied to the + * upstream issue, caused by an update of a downstream issue, when + * there's no compatible backwards mapping. + */ + Optional defaultUpstreamResolution(); + } + interface UserValueMapping extends ValueMapping { /** * @return the name of the property to apply the assignee value to. With Jira diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectContext.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectContext.java index 4145309..89cb0e9 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectContext.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectContext.java @@ -306,14 +306,6 @@ private static boolean versionNeedsUpdate(JiraVersion upstreamVersion, JiraVersi || !Objects.equals(upstreamVersion.releaseDate, downstreamVersion.releaseDate); } - public String upstreamUser(String mappedValue) { - return projectGroupContext.upstreamUser(mappedValue); - } - - public String upstreamStatus(String mappedValue) { - return projectGroupContext.upstreamStatus(mappedValue); - } - public String toDestinationKey(String key) { if (keyToUpdatePattern.matcher(key).matches()) { return "%s-%d".formatted(project().projectKey(), JiraIssue.keyToLong(key)); diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectGroupContext.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectGroupContext.java index 0024421..d62cc5c 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectGroupContext.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectGroupContext.java @@ -39,6 +39,7 @@ public final class HandlerProjectGroupContext implements AutoCloseable { private final JiraConfig.JiraProjectGroup projectGroup; private final Map invertedUsers; private final Map invertedStatuses; + private final Map invertedResolutions; private final Map projectContexts; private final Pattern sourceLabelPattern; private final JiraUser notMappedAssignee; @@ -73,6 +74,7 @@ public HandlerProjectGroupContext(String projectGroupName, JiraConfig.JiraProjec this.invertedUsers = invert(projectGroup.users().mapping()); this.invertedStatuses = invert(projectGroup.statuses().mapping()); + this.invertedResolutions = invert(projectGroup.resolutions().mapping()); this.sourceJiraClient = source; this.destinationJiraClient = destination; @@ -209,6 +211,10 @@ public String upstreamStatus(String mappedValue) { return invertedStatuses.get(mappedValue); } + public String upstreamResolution(String mappedValue) { + return invertedResolutions.get(mappedValue); + } + public boolean isUserIgnored(String triggeredByUser) { return projectGroup().users().ignoredUpstreamUsers().contains(triggeredByUser); } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java index 1ed635e..9895fc3 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java @@ -19,6 +19,7 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraRemoteLink; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraSimpleObject; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransition; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransitionFields; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransitions; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraUser; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraVersion; @@ -44,7 +45,7 @@ protected void applyTransition(JiraIssue sourceIssue, JiraIssue destIssue, Strin // no need to apply the transition :) return; } - prepareTransition(sourceIssue.fields.status, destIssue).ifPresent( + prepareTransition(sourceIssue.fields.status, sourceIssue.fields.resolution, destIssue).ifPresent( jiraTransition -> context.destinationJiraClient().transition(destinationKey, jiraTransition)); } @@ -229,10 +230,20 @@ private JiraUser toUser(String value) { return new JiraUser(context.projectGroup().users().mappedPropertyName(), value); } - protected Optional prepareTransition(JiraSimpleObject sourceStatus, JiraIssue destIssue) { + protected Optional prepareTransition(JiraSimpleObject sourceStatus, JiraSimpleObject resolution, + JiraIssue destIssue) { String downstreamStatus = context.projectGroup().statuses().mapping() .get(sourceStatus.name.toLowerCase(Locale.ROOT)); - return prepareTransition(downstreamStatus, destIssue); + Optional transition = prepareTransition(downstreamStatus, destIssue); + if (resolution != null) { + String downstreamResolution = context.projectGroup().resolutions().mapping() + .get(resolution.name.toLowerCase(Locale.ROOT)); + return transition.map(t -> { + t.fields = JiraTransitionFields.forResolution(downstreamResolution); + return t; + }); + } + return transition; } protected Optional prepareTransition(String downstreamStatus, JiraIssue destIssue) { diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueDeleteEventHandler.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueDeleteEventHandler.java index 320d5e0..7e88126 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueDeleteEventHandler.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueDeleteEventHandler.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; @@ -11,7 +10,6 @@ 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; @@ -84,12 +82,9 @@ private Optional prepareTransition(JiraIssue issue) { Optional deletedStatus = context.projectGroup().statuses().deletedStatus(); if (deletedStatus.isPresent()) { prepareTransition(deletedStatus.get(), issue); - JiraTransition transition = new JiraTransition(); - transition.transition = new JiraIssueTransition(deletedStatus.get()); Optional deletedResolution = context.projectGroup().statuses().deletedResolution(); - deletedResolution.ifPresent( - name -> transition.properties().put("fields", Map.of("resolution", Map.of("name", name)))); + JiraTransition transition = new JiraTransition(deletedStatus.get(), deletedResolution.orElse(null)); return Optional.of(transition); } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/action/JiraTransitionActionEventHandler.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/action/JiraTransitionActionEventHandler.java index 0b6d0a3..fcc94ec 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/action/JiraTransitionActionEventHandler.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/action/JiraTransitionActionEventHandler.java @@ -28,6 +28,9 @@ protected void doRun() { String statusDownstream = issue.fields.status.name.toLowerCase(Locale.ROOT); String statusCurrent = sourceIssue.fields.status.name.toLowerCase(Locale.ROOT); + String resolutionDownstream = issue.fields.resolution != null + ? issue.fields.resolution.name.toLowerCase(Locale.ROOT) + : null; if (context.projectGroup().statuses().ignoreTransitionCondition().getOrDefault(statusCurrent, Set.of()) .contains(statusDownstream)) { @@ -35,15 +38,16 @@ protected void doRun() { } String statusNew = context.upstreamStatus(statusDownstream); + String resolution = context.upstreamResolution(resolutionDownstream); - prepareTransition(statusNew, sourceIssue) + prepareTransition(statusNew, resolution, sourceIssue) .ifPresent(jiraTransition -> context.sourceJiraClient().transition(sourceKey, jiraTransition)); } - protected Optional prepareTransition(String upstreamStatus, JiraIssue issue) { + protected Optional prepareTransition(String upstreamStatus, String resolution, JiraIssue issue) { return statusToTransition(issue.fields.status.name, upstreamStatus, () -> JiraTransitions .findRequiredTransitionId(context.sourceJiraClient(), failureCollector, upstreamStatus, issue)) - .map(JiraTransition::new); + .map(id -> new JiraTransition(id, resolution)); } protected Optional statusToTransition(String from, String to, Supplier> transitionFinder) { diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraFields.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraFields.java index d9cc16c..b0c22b2 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraFields.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraFields.java @@ -30,6 +30,7 @@ public class JiraFields extends JiraBaseObject { public JiraIssue parent; public ZonedDateTime created; public ZonedDateTime updated; + public JiraSimpleObject resolution; public static JiraFields empty() { JiraFields fields = new JiraFields(); @@ -42,8 +43,8 @@ public static JiraFields empty() { @Override public String toString() { return "JiraFields{" + "summary='" + summary + '\'' + ", description=" + description + ", priority=" + priority - + ", issuetype=" + issuetype + ", project=" + project + ", labels=" + labels + "otherProperties=" - + properties() + '}'; + + ", issuetype=" + issuetype + ", project=" + project + ", resolution=" + resolution + ", labels=" + + labels + "otherProperties=" + properties() + '}'; } } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraTransition.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraTransition.java index 871a029..c892a05 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraTransition.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraTransition.java @@ -9,16 +9,24 @@ public class JiraTransition extends JiraBaseObject { public JiraIssueTransition transition; public JiraUpdate update; + public JiraTransitionFields fields; public JiraTransition() { } public JiraTransition(String transitionId) { - this(transitionId, null); + this(transitionId, null, null); } - public JiraTransition(String transitionId, String comment) { + public JiraTransition(String transitionId, String resolution) { + this(transitionId, resolution, null); + } + + public JiraTransition(String transitionId, String resolution, String comment) { transition = new JiraIssueTransition(transitionId); + if (resolution != null) { + fields = JiraTransitionFields.forResolution(resolution); + } if (comment != null && !comment.isBlank()) { update = new JiraUpdate(); JiraBaseObject c = new JiraBaseObject(); diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraTransitionFields.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraTransitionFields.java new file mode 100644 index 0000000..6cd29b4 --- /dev/null +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraTransitionFields.java @@ -0,0 +1,25 @@ +package org.hibernate.infra.replicate.jira.service.jira.model.rest; + +import org.hibernate.infra.replicate.jira.service.jira.model.JiraBaseObject; + +public class JiraTransitionFields extends JiraBaseObject { + + public JiraSimpleObject resolution; + + public static JiraTransitionFields forResolution(String resolution) { + if (resolution == null) { + return null; + } + JiraTransitionFields fields = new JiraTransitionFields(); + fields.resolution = new JiraSimpleObject(); + fields.resolution.name = resolution; + return fields; + + } + + @Override + public String toString() { + return "JiraTransitionFields{" + "resolution=" + resolution + "otherProperties=" + properties() + '}'; + } + +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 0d5596b..2e2d158 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -4,6 +4,7 @@ jira.project-group."hibernate".priorities.default-value=3 jira.project-group."hibernate".statuses.default-value=1 jira.project-group."hibernate".issue-link-types.parent-link-type=10011 jira.project-group."hibernate".users.default-value=-1 +jira.project-group."hibernate".resolutions.default-value=Done jira.project-group."hibernate".scheduled.cron=off