@@ -86,8 +86,8 @@ public class FPOrAcceptedNotificationHandler extends EmailNotificationHandler<Is | |||
.map(serializer::from) | |||
// ignore notifications which contain no issue changed to a FP or Accepted status | |||
.filter(t -> t.getIssues().stream() | |||
.filter(issue -> issue.getNewIssueStatus().isPresent()) | |||
.anyMatch(issue -> FP_OR_ACCEPTED_SIMPLE_STATUSES.contains(issue.getNewIssueStatus().get()))) | |||
.filter(issue -> issue.getNewIssueStatus().isPresent() && issue.getOldIssueStatus().isPresent()) | |||
.anyMatch(issue -> !issue.getNewIssueStatus().equals(issue.getOldIssueStatus()) && FP_OR_ACCEPTED_SIMPLE_STATUSES.contains(issue.getNewIssueStatus().get()))) | |||
.map(NotificationWithProjectKeys::new) | |||
.collect(Collectors.toSet()); | |||
if (changeNotificationsWithFpOrAccepted.isEmpty()) { | |||
@@ -144,14 +144,14 @@ public class FPOrAcceptedNotificationHandler extends EmailNotificationHandler<Is | |||
.collect(unorderedIndex(t -> t.getNewIssueStatus().get(), issue -> issue)); | |||
return Stream.of( | |||
of(issuesByNewIssueStatus.get(IssueStatus.FALSE_POSITIVE)) | |||
.filter(t -> !t.isEmpty()) | |||
.map(fpIssues -> new FPOrAcceptedNotification(notification.getChange(), fpIssues, FP)) | |||
.orElse(null), | |||
of(issuesByNewIssueStatus.get(IssueStatus.ACCEPTED)) | |||
.filter(t -> !t.isEmpty()) | |||
.map(acceptedIssues -> new FPOrAcceptedNotification(notification.getChange(), acceptedIssues, ACCEPTED)) | |||
.orElse(null)) | |||
of(issuesByNewIssueStatus.get(IssueStatus.FALSE_POSITIVE)) | |||
.filter(t -> !t.isEmpty()) | |||
.map(fpIssues -> new FPOrAcceptedNotification(notification.getChange(), fpIssues, FP)) | |||
.orElse(null), | |||
of(issuesByNewIssueStatus.get(IssueStatus.ACCEPTED)) | |||
.filter(t -> !t.isEmpty()) | |||
.map(acceptedIssues -> new FPOrAcceptedNotification(notification.getChange(), acceptedIssues, ACCEPTED)) | |||
.orElse(null)) | |||
.filter(Objects::nonNull) | |||
.map(fpOrAcceptedNotification -> new EmailDeliveryRequest(recipient.email(), fpOrAcceptedNotification)); | |||
}); |
@@ -65,11 +65,13 @@ public class IssuesChangesNotificationBuilder { | |||
private final User assignee; | |||
private final Rule rule; | |||
private final Project project; | |||
private final IssueStatus oldIssueStatus; | |||
public ChangedIssue(Builder builder) { | |||
this.key = requireNonNull(builder.key, KEY_CANT_BE_NULL_MESSAGE); | |||
this.newStatus = requireNonNull(builder.newStatus, "newStatus can't be null"); | |||
this.newIssueStatus = builder.newIssueStatus; | |||
this.oldIssueStatus = builder.oldIssueStatus; | |||
this.assignee = builder.assignee; | |||
this.rule = requireNonNull(builder.rule, "rule can't be null"); | |||
this.project = requireNonNull(builder.project, "project can't be null"); | |||
@@ -87,6 +89,10 @@ public class IssuesChangesNotificationBuilder { | |||
return ofNullable(newIssueStatus); | |||
} | |||
public Optional<IssueStatus> getOldIssueStatus() { | |||
return ofNullable(oldIssueStatus); | |||
} | |||
public Optional<User> getAssignee() { | |||
return ofNullable(assignee); | |||
} | |||
@@ -103,6 +109,8 @@ public class IssuesChangesNotificationBuilder { | |||
private final String key; | |||
@CheckForNull | |||
private IssueStatus newIssueStatus; | |||
@CheckForNull | |||
private IssueStatus oldIssueStatus; | |||
private String newStatus; | |||
@CheckForNull | |||
private User assignee; | |||
@@ -128,6 +136,11 @@ public class IssuesChangesNotificationBuilder { | |||
return this; | |||
} | |||
public Builder setOldIssueStatus(@Nullable IssueStatus oldIssueStatus) { | |||
this.oldIssueStatus = oldIssueStatus; | |||
return this; | |||
} | |||
public Builder setRule(Rule rule) { | |||
this.rule = rule; | |||
return this; |
@@ -89,6 +89,7 @@ public class IssuesChangesNotificationSerializer { | |||
.map(issue -> new ChangedIssue.Builder(issue.key) | |||
.setNewStatus(issue.newStatus) | |||
.setNewIssueStatus(issue.newIssueStatus == null ? null : IssueStatus.valueOf(issue.newIssueStatus)) | |||
.setOldIssueStatus(issue.oldIssueStatus == null ? null : IssueStatus.valueOf(issue.oldIssueStatus)) | |||
.setAssignee(issue.assignee) | |||
.setRule(rules.get(issue.ruleKey)) | |||
.setProject(projects.get(issue.projectUuid)) | |||
@@ -125,6 +126,8 @@ public class IssuesChangesNotificationSerializer { | |||
notification.setFieldValue(issuePropertyPrefix + ".newStatus", issue.getNewStatus()); | |||
issue.getNewIssueStatus() | |||
.ifPresent(newIssueStatus -> notification.setFieldValue(issuePropertyPrefix + ".newIssueStatus", newIssueStatus.name())); | |||
issue.getOldIssueStatus() | |||
.ifPresent(oldIssueStatus -> notification.setFieldValue(issuePropertyPrefix + ".oldIssueStatus", oldIssueStatus.name())); | |||
notification.setFieldValue(issuePropertyPrefix + ".ruleKey", issue.getRule().getKey().toString()); | |||
notification.setFieldValue(issuePropertyPrefix + ".projectUuid", issue.getProject().getUuid()); | |||
} | |||
@@ -137,6 +140,7 @@ public class IssuesChangesNotificationSerializer { | |||
.setNewStatus(getIssueFieldValue(notification, issuePropertyPrefix + ".newStatus", index)) | |||
.setNewResolution(notification.getFieldValue(issuePropertyPrefix + ".newResolution")) | |||
.setNewIssueStatus(notification.getFieldValue(issuePropertyPrefix + ".newIssueStatus")) | |||
.setOldIssueStatus(notification.getFieldValue(issuePropertyPrefix + ".oldIssueStatus")) | |||
.setAssignee(assignee) | |||
.setRuleKey(getIssueFieldValue(notification, issuePropertyPrefix + ".ruleKey", index)) | |||
.setProjectUuid(getIssueFieldValue(notification, issuePropertyPrefix + ".projectUuid", index)) | |||
@@ -254,6 +258,8 @@ public class IssuesChangesNotificationSerializer { | |||
@CheckForNull | |||
private final String newIssueStatus; | |||
@CheckForNull | |||
private final String oldIssueStatus; | |||
@CheckForNull | |||
private final User assignee; | |||
private final RuleKey ruleKey; | |||
private final String projectUuid; | |||
@@ -266,9 +272,11 @@ public class IssuesChangesNotificationSerializer { | |||
this.ruleKey = RuleKey.parse(builder.ruleKey); | |||
this.projectUuid = builder.projectUuid; | |||
this.newIssueStatus = builder.newIssueStatus; | |||
this.oldIssueStatus = builder.oldIssueStatus; | |||
} | |||
static class Builder { | |||
private String oldIssueStatus = null; | |||
private String key = null; | |||
private String newStatus = null; | |||
@CheckForNull | |||
@@ -299,6 +307,11 @@ public class IssuesChangesNotificationSerializer { | |||
return this; | |||
} | |||
public Builder setOldIssueStatus(@Nullable String oldIssueStatus) { | |||
this.oldIssueStatus = oldIssueStatus; | |||
return this; | |||
} | |||
public Builder setAssignee(@Nullable User assignee) { | |||
this.assignee = assignee; | |||
return this; |
@@ -24,7 +24,6 @@ import com.google.common.collect.ListMultimap; | |||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||
import java.util.Random; | |||
import java.util.Set; | |||
import java.util.function.Consumer; | |||
import java.util.stream.IntStream; | |||
@@ -62,7 +61,6 @@ import static org.mockito.Mockito.verifyNoInteractions; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; | |||
import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX; | |||
import static org.sonar.core.util.stream.MoreCollectors.index; | |||
import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newProject; | |||
import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule; | |||
@@ -114,7 +112,7 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
@Test | |||
public void deliver_has_no_effect_if_emailNotificationChannel_is_disabled() { | |||
when(emailNotificationChannel.isActivated()).thenReturn(false); | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10)) | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 5) | |||
.mapToObj(i -> mock(IssuesChangesNotification.class)) | |||
.collect(toSet()); | |||
@@ -129,7 +127,7 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
@Test | |||
public void deliver_parses_every_notification_in_order() { | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 5 + new Random().nextInt(10)) | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 10) | |||
.mapToObj(i -> mock(IssuesChangesNotification.class)) | |||
.collect(toSet()); | |||
when(emailNotificationChannel.isActivated()).thenReturn(true); | |||
@@ -143,7 +141,7 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
@Test | |||
public void deliver_fails_with_IAE_if_serializer_throws_IAE() { | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 3 + new Random().nextInt(10)) | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 10) | |||
.mapToObj(i -> mock(IssuesChangesNotification.class)) | |||
.collect(toSet()); | |||
when(emailNotificationChannel.isActivated()).thenReturn(true); | |||
@@ -168,8 +166,31 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
public void deliver_has_no_effect_if_no_issue_has_FP_or_wontfix_resolution(IssueStatus newIssueStatus) { | |||
when(emailNotificationChannel.isActivated()).thenReturn(true); | |||
Change changeMock = mock(Change.class); | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 2 + new Random().nextInt(5)) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder(randomIssues(t -> t.setNewIssueStatus(newIssueStatus)).collect(toSet()), changeMock)) | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 10) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder(streamOfIssues(t -> t.setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN)).collect(toSet()), changeMock)) | |||
.map(serializer::serialize) | |||
.collect(toSet()); | |||
reset(serializer); | |||
int deliver = underTest.deliver(notifications); | |||
assertThat(deliver).isZero(); | |||
verify(serializer, times(notifications.size())).from(any(IssuesChangesNotification.class)); | |||
verifyNoInteractions(changeMock); | |||
verifyNoMoreInteractions(serializer); | |||
verifyNoInteractions(notificationManager); | |||
verify(emailNotificationChannel).isActivated(); | |||
verifyNoMoreInteractions(emailNotificationChannel); | |||
} | |||
@Test | |||
@UseDataProvider("FPorWontFixResolutionWithCorrespondingIssueStatus") | |||
public void deliver_shouldNotSendNotification_WhenIssueStatusHasNotChanged(String newResolution, | |||
IssueStatus newIssueStatus) { | |||
when(emailNotificationChannel.isActivated()).thenReturn(true); | |||
Change changeMock = mock(Change.class); | |||
Set<IssuesChangesNotification> notifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder(streamOfIssues(t -> t.setNewIssueStatus(newIssueStatus).setOldIssueStatus(newIssueStatus)).collect(toSet()), changeMock)) | |||
.map(serializer::serialize) | |||
.collect(toSet()); | |||
reset(serializer); | |||
@@ -204,21 +225,21 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
Project projectKey4 = newProject(randomAlphabetic(7)); | |||
Change changeMock = mock(Change.class); | |||
// some notifications with some issues on project1 | |||
Stream<IssuesChangesNotificationBuilder> project1Notifications = IntStream.range(0, 1 + new Random().nextInt(2)) | |||
Stream<IssuesChangesNotificationBuilder> project1Notifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder( | |||
randomIssues(t -> t.setProject(projectKey1).setNewIssueStatus(newIssueStatus)).collect(toSet()), | |||
streamOfIssues(t -> t.setProject(projectKey1).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN)).collect(toSet()), | |||
changeMock)); | |||
// some notifications with some issues on project2 | |||
Stream<IssuesChangesNotificationBuilder> project2Notifications = IntStream.range(0, 1 + new Random().nextInt(2)) | |||
Stream<IssuesChangesNotificationBuilder> project2Notifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder( | |||
randomIssues(t -> t.setProject(projectKey2).setNewIssueStatus(newIssueStatus)).collect(toSet()), | |||
streamOfIssues(t -> t.setProject(projectKey2).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN)).collect(toSet()), | |||
changeMock)); | |||
// some notifications with some issues on project3 and project 4 | |||
Stream<IssuesChangesNotificationBuilder> project3And4Notifications = IntStream.range(0, 1 + new Random().nextInt(2)) | |||
Stream<IssuesChangesNotificationBuilder> project3And4Notifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder( | |||
Stream.concat( | |||
randomIssues(t -> t.setProject(projectKey3).setNewIssueStatus(newIssueStatus)), | |||
randomIssues(t -> t.setProject(projectKey4).setNewIssueStatus(newIssueStatus))) | |||
streamOfIssues(t -> t.setProject(projectKey3).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN)), | |||
streamOfIssues(t -> t.setProject(projectKey4).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN))) | |||
.collect(toSet()), | |||
changeMock)); | |||
when(emailNotificationChannel.isActivated()).thenReturn(true); | |||
@@ -250,40 +271,40 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
User otherChangeAuthor = newUser("otherChangeAuthor"); | |||
// subscriber1 is the changeAuthor of some notifications with issues assigned to subscriber1 only | |||
Set<IssuesChangesNotificationBuilder> subscriber1Notifications = IntStream.range(0, 1 + new Random().nextInt(2)) | |||
Set<IssuesChangesNotificationBuilder> subscriber1Notifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder( | |||
randomIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setAssignee(subscriber2)).collect(toSet()), | |||
streamOfIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN).setAssignee(subscriber2)).collect(toSet()), | |||
newUserChange(subscriber1))) | |||
.collect(toSet()); | |||
// subscriber1 is the changeAuthor of some notifications with issues assigned to subscriber1 and subscriber2 | |||
Set<IssuesChangesNotificationBuilder> subscriber1and2Notifications = IntStream.range(0, 1 + new Random().nextInt(2)) | |||
Set<IssuesChangesNotificationBuilder> subscriber1and2Notifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder( | |||
Stream.concat( | |||
randomIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setAssignee(subscriber2)), | |||
randomIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setAssignee(subscriber1))) | |||
streamOfIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN).setAssignee(subscriber2)), | |||
streamOfIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN).setAssignee(subscriber1))) | |||
.collect(toSet()), | |||
newUserChange(subscriber1))) | |||
.collect(toSet()); | |||
// subscriber2 is the changeAuthor of some notifications with issues assigned to subscriber2 only | |||
Set<IssuesChangesNotificationBuilder> subscriber2Notifications = IntStream.range(0, 1 + new Random().nextInt(2)) | |||
Set<IssuesChangesNotificationBuilder> subscriber2Notifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder( | |||
randomIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setAssignee(subscriber2)).collect(toSet()), | |||
streamOfIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN).setAssignee(subscriber2)).collect(toSet()), | |||
newUserChange(subscriber2))) | |||
.collect(toSet()); | |||
// subscriber2 is the changeAuthor of some notifications with issues assigned to subscriber2 and subscriber 3 | |||
Set<IssuesChangesNotificationBuilder> subscriber2And3Notifications = IntStream.range(0, 1 + new Random().nextInt(2)) | |||
Set<IssuesChangesNotificationBuilder> subscriber2And3Notifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder( | |||
Stream.concat( | |||
randomIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setAssignee(subscriber2)), | |||
randomIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setAssignee(subscriber3))) | |||
streamOfIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN).setAssignee(subscriber2)), | |||
streamOfIssues(t -> t.setProject(project).setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN).setAssignee(subscriber3))) | |||
.collect(toSet()), | |||
newUserChange(subscriber2))) | |||
.collect(toSet()); | |||
// subscriber3 is the changeAuthor of no notification | |||
// otherChangeAuthor has some notifications | |||
Set<IssuesChangesNotificationBuilder> otherChangeAuthorNotifications = IntStream.range(0, 1 + new Random().nextInt(2)) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder(randomIssues(t -> t.setProject(project) | |||
.setNewIssueStatus(newIssueStatus)).collect(toSet()), | |||
Set<IssuesChangesNotificationBuilder> otherChangeAuthorNotifications = IntStream.range(0, 5) | |||
.mapToObj(j -> new IssuesChangesNotificationBuilder(streamOfIssues(t -> t.setProject(project) | |||
.setNewIssueStatus(newIssueStatus).setOldIssueStatus(IssueStatus.OPEN)).collect(toSet()), | |||
newUserChange(otherChangeAuthor))) | |||
.collect(toSet()); | |||
when(emailNotificationChannel.isActivated()).thenReturn(true); | |||
@@ -292,7 +313,7 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
when(notificationManager.findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, project.getKey(), ALL_MUST_HAVE_ROLE_USER)) | |||
.thenReturn(subscriberLogins.stream().map(FPOrAcceptedNotificationHandlerTest::emailRecipientOf).collect(toSet())); | |||
int deliveredCount = new Random().nextInt(200); | |||
int deliveredCount = 200; | |||
when(emailNotificationChannel.deliverAll(anySet())) | |||
.thenReturn(deliveredCount) | |||
.thenThrow(new IllegalStateException("deliver should be called only once")); | |||
@@ -368,12 +389,12 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
User changeAuthor = newUser("changeAuthor"); | |||
Set<ChangedIssue> fpIssues = projects.stream() | |||
.flatMap(project -> randomIssues(t -> t.setProject(project) | |||
.setNewIssueStatus(IssueStatus.FALSE_POSITIVE).setAssignee(subscriber1))) | |||
.flatMap(project -> streamOfIssues(t -> t.setProject(project) | |||
.setNewIssueStatus(IssueStatus.FALSE_POSITIVE).setOldIssueStatus(IssueStatus.OPEN).setAssignee(subscriber1))) | |||
.collect(toSet()); | |||
Set<ChangedIssue> wontFixIssues = projects.stream() | |||
.flatMap(project -> randomIssues(t -> t.setProject(project) | |||
.setNewIssueStatus(IssueStatus.ACCEPTED).setAssignee(subscriber1))) | |||
.flatMap(project -> streamOfIssues(t -> t.setProject(project) | |||
.setNewIssueStatus(IssueStatus.ACCEPTED).setOldIssueStatus(IssueStatus.OPEN).setAssignee(subscriber1))) | |||
.collect(toSet()); | |||
UserChange userChange = newUserChange(changeAuthor); | |||
IssuesChangesNotificationBuilder fpAndWontFixNotifications = new IssuesChangesNotificationBuilder( | |||
@@ -383,7 +404,7 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
projects.forEach(project -> when(notificationManager.findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, project.getKey(), ALL_MUST_HAVE_ROLE_USER)) | |||
.thenReturn(singleton(emailRecipientOf(subscriber1.getLogin())))); | |||
int deliveredCount = new Random().nextInt(200); | |||
int deliveredCount = 200; | |||
when(emailNotificationChannel.deliverAll(anySet())) | |||
.thenReturn(deliveredCount) | |||
.thenThrow(new IllegalStateException("deliver should be called only once")); | |||
@@ -414,7 +435,7 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
public static Object[][] oneOrMoreProjectCounts() { | |||
return new Object[][] { | |||
{1}, | |||
{2 + new Random().nextInt(3)}, | |||
{5}, | |||
}; | |||
} | |||
@@ -452,8 +473,8 @@ public class FPOrAcceptedNotificationHandlerTest { | |||
}; | |||
} | |||
private static Stream<ChangedIssue> randomIssues(Consumer<ChangedIssue.Builder> consumer) { | |||
return IntStream.range(0, 1 + new Random().nextInt(5)) | |||
private static Stream<ChangedIssue> streamOfIssues(Consumer<ChangedIssue.Builder> consumer) { | |||
return IntStream.range(0, 5) | |||
.mapToObj(i -> { | |||
ChangedIssue.Builder builder = new ChangedIssue.Builder("key_" + i) | |||
.setAssignee(new User(randomAlphabetic(3), randomAlphabetic(4), randomAlphabetic(5))) |
@@ -242,6 +242,7 @@ public class AddCommentActionIT { | |||
verify(issueFieldsSetter).addComment(defaultIssueCaptor.capture(), eq(comment), eq(issueChangeContext)); | |||
verify(issueUpdater).saveIssueAndPreloadSearchResponseData( | |||
any(DbSession.class), | |||
any(IssueDto.class), | |||
defaultIssueCaptor.capture(), | |||
eq(issueChangeContext)); | |||
@@ -566,6 +566,7 @@ public class AssignActionIT { | |||
verify(issueFieldsSetter).assign(defaultIssueCaptor.capture(), userMatcher(assignee), any(IssueChangeContext.class)); | |||
verify(issueUpdater).saveIssueAndPreloadSearchResponseData( | |||
any(DbSession.class), | |||
any(IssueDto.class), | |||
defaultIssueCaptor.capture(), | |||
any(IssueChangeContext.class)); | |||
@@ -441,6 +441,7 @@ public class ChangeStatusActionIT { | |||
if (transitionDone) { | |||
verify(issueUpdater).saveIssueAndPreloadSearchResponseData( | |||
any(DbSession.class), | |||
any(IssueDto.class), | |||
defaultIssueCaptor.capture(), | |||
eq(issueChangeContext)); | |||
@@ -546,6 +547,7 @@ public class ChangeStatusActionIT { | |||
if (transitionDone) { | |||
verify(issueUpdater).saveIssueAndPreloadSearchResponseData( | |||
any(DbSession.class), | |||
any(IssueDto.class), | |||
defaultIssueCaptor.capture(), | |||
eq(issueChangeContext)); | |||
@@ -595,6 +597,7 @@ public class ChangeStatusActionIT { | |||
verify(issueFieldsSetter).addComment(defaultIssueCaptor.capture(), eq(comment), eq(issueChangeContext)); | |||
verify(issueUpdater).saveIssueAndPreloadSearchResponseData( | |||
any(DbSession.class), | |||
any(IssueDto.class), | |||
defaultIssueCaptor.capture(), | |||
eq(issueChangeContext)); | |||
@@ -32,6 +32,7 @@ import org.sonar.api.impl.utils.TestSystem2; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.issue.status.IssueStatus; | |||
import org.sonar.core.util.SequenceUuidFactory; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbTester; | |||
@@ -418,6 +419,8 @@ public class BulkChangeActionIT { | |||
ChangedIssue changedIssue = builder.getIssues().iterator().next(); | |||
assertThat(changedIssue.getKey()).isEqualTo(issue.getKey()); | |||
assertThat(changedIssue.getNewStatus()).isEqualTo(STATUS_CONFIRMED); | |||
assertThat(changedIssue.getNewIssueStatus()).contains(IssueStatus.CONFIRMED); | |||
assertThat(changedIssue.getOldIssueStatus()).contains(IssueStatus.OPEN); | |||
assertThat(changedIssue.getAssignee()).isEmpty(); | |||
assertThat(changedIssue.getRule()).isEqualTo(ruleOf(rule)); | |||
assertThat(changedIssue.getProject()).isEqualTo(projectBranchOf(db, branch)); | |||
@@ -484,6 +487,8 @@ public class BulkChangeActionIT { | |||
ChangedIssue changedIssue = builder.getIssues().iterator().next(); | |||
assertThat(changedIssue.getKey()).isEqualTo(issue3.getKey()); | |||
assertThat(changedIssue.getNewStatus()).isEqualTo(STATUS_OPEN); | |||
assertThat(changedIssue.getNewIssueStatus()).contains(IssueStatus.OPEN); | |||
assertThat(changedIssue.getOldIssueStatus()).contains(IssueStatus.OPEN); | |||
assertThat(changedIssue.getAssignee()).isEmpty(); | |||
assertThat(changedIssue.getRule()).isEqualTo(ruleOf(rule)); | |||
assertThat(changedIssue.getProject()).isEqualTo(projectOf(project)); |
@@ -55,6 +55,7 @@ import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoInteractions; | |||
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; | |||
import static org.sonar.api.issue.Issue.STATUS_RESOLVED; | |||
import static org.sonar.api.rule.Severity.BLOCKER; | |||
import static org.sonar.api.rule.Severity.MAJOR; | |||
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder; | |||
@@ -90,12 +91,13 @@ public class IssueUpdaterIT { | |||
@Test | |||
public void update_issue() { | |||
DefaultIssue issue = db.issues().insertIssue(i -> i.setSeverity(MAJOR)).toDefaultIssue(); | |||
IssueDto originalIssueDto = db.issues().insertIssue(i -> i.setSeverity(MAJOR)); | |||
DefaultIssue issue = originalIssueDto.toDefaultIssue(); | |||
UserDto user = db.users().insertUser(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), user.getUuid()).build(); | |||
issueFieldsSetter.setSeverity(issue, BLOCKER, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
IssueDto issueReloaded = dbClient.issueDao().selectByKey(db.getSession(), issue.key()).get(); | |||
assertThat(issueReloaded.getSeverity()).isEqualTo(BLOCKER); | |||
@@ -107,14 +109,15 @@ public class IssueUpdaterIT { | |||
RuleDto rule = db.rules().insertIssueRule(); | |||
ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
DefaultIssue issue = db.issues().insertIssue(rule, project, file, | |||
t -> t.setSeverity(MAJOR).setAssigneeUuid(assignee.getUuid())) | |||
IssueDto originalIssueDto = db.issues().insertIssue(rule, project, file, | |||
t -> t.setSeverity(MAJOR).setAssigneeUuid(assignee.getUuid())); | |||
DefaultIssue issue = originalIssueDto | |||
.toDefaultIssue(); | |||
UserDto changeAuthor = db.users().insertUser(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).build(); | |||
issueFieldsSetter.setSeverity(issue, BLOCKER, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); | |||
IssuesChangesNotification issueChangeNotification = notificationArgumentCaptor.getValue(); | |||
@@ -135,14 +138,16 @@ public class IssueUpdaterIT { | |||
RuleDto rule = db.rules().insertIssueRule(); | |||
ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
DefaultIssue issue = db.issues().insertIssue(rule, project, file, | |||
t -> t.setSeverity(MAJOR).setAssigneeUuid(assignee.getUuid())) | |||
IssueDto originalIssueDto = db.issues().insertIssue(rule, project, file, | |||
t -> t.setSeverity(MAJOR).setAssigneeUuid(assignee.getUuid())); | |||
DefaultIssue issue = originalIssueDto | |||
.toDefaultIssue(); | |||
UserDto changeAuthor = db.users().insertUser(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).build(); | |||
issueFieldsSetter.setResolution(issue, RESOLUTION_FIXED, context); | |||
issueFieldsSetter.setStatus(issue, STATUS_RESOLVED, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); | |||
IssuesChangesNotification issueChangeNotification = notificationArgumentCaptor.getValue(); | |||
@@ -151,6 +156,8 @@ public class IssueUpdaterIT { | |||
ChangedIssue changedIssue = builder.getIssues().iterator().next(); | |||
assertThat(changedIssue.getKey()).isEqualTo(issue.key()); | |||
assertThat(changedIssue.getNewStatus()).isEqualTo(issue.status()); | |||
assertThat(changedIssue.getOldIssueStatus()).contains(originalIssueDto.getIssueStatus()); | |||
assertThat(changedIssue.getNewIssueStatus()).contains(issue.getIssueStatus()); | |||
assertThat(changedIssue.getAssignee()).contains(userOf(assignee)); | |||
assertThat(changedIssue.getRule()).isEqualTo(ruleOf(rule)); | |||
assertThat(changedIssue.getProject()).isEqualTo(projectOf(project)); | |||
@@ -163,13 +170,14 @@ public class IssueUpdaterIT { | |||
ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); | |||
ComponentDto branch = db.components().insertProjectBranch(project, t -> t.setBranchType(BRANCH)); | |||
ComponentDto file = db.components().insertComponent(newFileDto(branch, project.uuid())); | |||
DefaultIssue issue = db.issues().insertIssue(rule, branch, file, | |||
t -> t.setSeverity(MAJOR)).toDefaultIssue(); | |||
IssueDto originalIssueDto = db.issues().insertIssue(rule, branch, file, | |||
t -> t.setSeverity(MAJOR)); | |||
DefaultIssue issue = originalIssueDto.toDefaultIssue(); | |||
UserDto changeAuthor = db.users().insertUser(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).build(); | |||
issueFieldsSetter.setSeverity(issue, BLOCKER, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); | |||
IssuesChangesNotification issueChangeNotification = notificationArgumentCaptor.getValue(); | |||
@@ -190,11 +198,12 @@ public class IssueUpdaterIT { | |||
ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); | |||
ComponentDto branch = db.components().insertProjectBranch(project, t -> t.setBranchType(BranchType.PULL_REQUEST)); | |||
ComponentDto file = db.components().insertComponent(newFileDto(branch, project.uuid())); | |||
DefaultIssue issue = db.issues().insertIssue(rule, branch, file, t -> t.setSeverity(MAJOR)).toDefaultIssue(); | |||
IssueDto originalIssueDto = db.issues().insertIssue(rule, branch, file, t -> t.setSeverity(MAJOR)); | |||
DefaultIssue issue = originalIssueDto.toDefaultIssue(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), "user_uuid").build(); | |||
issueFieldsSetter.setSeverity(issue, BLOCKER, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
verifyNoInteractions(notificationManager); | |||
} | |||
@@ -204,11 +213,12 @@ public class IssueUpdaterIT { | |||
RuleDto rule = db.rules().insertIssueRule(r -> r.setStatus(RuleStatus.REMOVED)); | |||
ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
DefaultIssue issue = db.issues().insertIssue(rule, project, file, t -> t.setSeverity(MAJOR)).toDefaultIssue(); | |||
IssueDto originalIssueDto = db.issues().insertIssue(rule, project, file, t -> t.setSeverity(MAJOR)); | |||
DefaultIssue issue = originalIssueDto.toDefaultIssue(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), "user_uuid").build(); | |||
issueFieldsSetter.setSeverity(issue, BLOCKER, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
verifyNoInteractions(notificationManager); | |||
} | |||
@@ -219,14 +229,15 @@ public class IssueUpdaterIT { | |||
RuleDto rule = db.rules().insertIssueRule(); | |||
ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
DefaultIssue issue = db.issues().insertIssue(rule, project, file, t -> t.setAssigneeUuid(oldAssignee.getUuid())) | |||
IssueDto originalIssueDto = db.issues().insertIssue(rule, project, file, t -> t.setAssigneeUuid(oldAssignee.getUuid())); | |||
DefaultIssue issue = originalIssueDto | |||
.toDefaultIssue(); | |||
UserDto changeAuthor = db.users().insertUser(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).build(); | |||
UserDto newAssignee = db.users().insertUser(); | |||
issueFieldsSetter.assign(issue, newAssignee, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); | |||
IssuesChangesNotification issueChangeNotification = notificationArgumentCaptor.getValue(); | |||
@@ -246,18 +257,18 @@ public class IssueUpdaterIT { | |||
RuleDto rule = db.rules().insertIssueRule(); | |||
ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
IssueDto issueDto = db.issues().insertIssue(rule, project, file); | |||
DefaultIssue issue = issueDto.setSeverity(MAJOR).toDefaultIssue(); | |||
IssueDto originalIssueDto = db.issues().insertIssue(rule, project, file); | |||
DefaultIssue issue = originalIssueDto.setSeverity(MAJOR).toDefaultIssue(); | |||
UserDto changeAuthor = db.users().insertUser(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).withRefreshMeasures().build(); | |||
issueFieldsSetter.setSeverity(issue, BLOCKER, context); | |||
SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
assertThat(preloadedSearchResponseData.getIssues()) | |||
.hasSize(1); | |||
assertThat(preloadedSearchResponseData.getIssues().iterator().next()) | |||
.isNotSameAs(issueDto); | |||
.isNotSameAs(originalIssueDto); | |||
assertThat(preloadedSearchResponseData.getRules()) | |||
.extracting(RuleDto::getKey) | |||
.containsOnly(rule.getKey()); | |||
@@ -272,17 +283,17 @@ public class IssueUpdaterIT { | |||
RuleDto rule = db.rules().insertIssueRule(r -> r.setStatus(RuleStatus.REMOVED)); | |||
ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
IssueDto issueDto = db.issues().insertIssue(rule, project, file); | |||
DefaultIssue issue = issueDto.setSeverity(MAJOR).toDefaultIssue(); | |||
IssueDto originalIssueDto = db.issues().insertIssue(rule, project, file); | |||
DefaultIssue issue = originalIssueDto.setSeverity(MAJOR).toDefaultIssue(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), "user_uuid").build(); | |||
issueFieldsSetter.setSeverity(issue, BLOCKER, context); | |||
SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context); | |||
SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), originalIssueDto, issue, context); | |||
assertThat(preloadedSearchResponseData.getIssues()) | |||
.hasSize(1); | |||
assertThat(preloadedSearchResponseData.getIssues().iterator().next()) | |||
.isNotSameAs(issueDto); | |||
.isNotSameAs(originalIssueDto); | |||
assertThat(preloadedSearchResponseData.getRules()).isNullOrEmpty(); | |||
assertThat(preloadedSearchResponseData.getComponents()) | |||
.extracting(ComponentDto::uuid) |
@@ -85,7 +85,7 @@ public class AddCommentAction implements HotspotsWsAction { | |||
DefaultIssue defaultIssue = hotspot.toDefaultIssue(); | |||
IssueChangeContext context = hotspotWsSupport.newIssueChangeContextWithoutMeasureRefresh(); | |||
issueFieldsSetter.addComment(defaultIssue, comment, context); | |||
issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context); | |||
issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, hotspot, defaultIssue, context); | |||
response.noContent(); | |||
} | |||
} |
@@ -126,7 +126,7 @@ public class AssignAction implements HotspotsWsAction { | |||
} | |||
if (issueFieldsSetter.assign(defaultIssue, assignee, context)) { | |||
issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context); | |||
issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, hotspotDto, defaultIssue, context); | |||
BranchDto branch = issueUpdater.getBranch(dbSession, defaultIssue); | |||
if (BRANCH.equals(branch.getBranchType())) { |
@@ -162,7 +162,7 @@ public class ChangeStatusAction implements HotspotsWsAction { | |||
issueFieldsSetter.addComment(defaultIssue, comment, context); | |||
} | |||
issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context); | |||
issueUpdater.saveIssueAndPreloadSearchResponseData(session, issueDto, defaultIssue, context); | |||
BranchDto branch = issueUpdater.getBranch(session, defaultIssue); | |||
if (BRANCH.equals(branch.getBranchType())) { |
@@ -103,7 +103,7 @@ public class AddCommentAction implements IssuesWsAction { | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(system2.now()), userSession.getUuid()).build(); | |||
DefaultIssue defaultIssue = issueDto.toDefaultIssue(); | |||
issueFieldsSetter.addComment(defaultIssue, wsRequest.getText(), context); | |||
SearchResponseData preloadedSearchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context); | |||
SearchResponseData preloadedSearchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, issueDto, defaultIssue, context); | |||
responseWriter.write(defaultIssue.key(), preloadedSearchResponseData, request, response); | |||
} | |||
} |
@@ -111,7 +111,7 @@ public class AssignAction implements IssuesWsAction { | |||
UserDto user = getUser(dbSession, login); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(system2.now()), userSession.getUuid()).build(); | |||
if (issueFieldsSetter.assign(issue, user, context)) { | |||
return issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, issue, context); | |||
return issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, issueDto, issue, context); | |||
} | |||
return new SearchResponseData(issueDto); | |||
} |
@@ -47,7 +47,6 @@ import org.sonar.api.utils.System2; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.status.IssueStatus; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.BranchDto; | |||
@@ -152,7 +151,7 @@ public class BulkChangeAction implements IssuesWsAction { | |||
public void define(WebService.NewController context) { | |||
WebService.NewAction action = context.createAction(ACTION_BULK_CHANGE) | |||
.setDescription("Bulk change on issues. Up to 500 issues can be updated. <br/>" + | |||
"Requires authentication.") | |||
"Requires authentication.") | |||
.setSince("3.7") | |||
.setChangelog( | |||
new Change("10.4", ("Transitions '%s' and '%s' are now deprecated. Use transition '%s' instead. " + | |||
@@ -196,7 +195,7 @@ public class BulkChangeAction implements IssuesWsAction { | |||
.setExampleValue("security,java8"); | |||
action.createParam(PARAM_COMMENT) | |||
.setDescription("Add a comment. " | |||
+ "The comment will only be added to issues that are affected either by a change of type or a change of severity as a result of the same WS call.") | |||
+ "The comment will only be added to issues that are affected either by a change of type or a change of severity as a result of the same WS call.") | |||
.setExampleValue("Here is my comment"); | |||
action.createParam(PARAM_SEND_NOTIFICATIONS) | |||
.setSince("4.0") | |||
@@ -334,11 +333,13 @@ public class BulkChangeAction implements IssuesWsAction { | |||
if (ruleDefinitionDto == null || projectDto == null) { | |||
return null; | |||
} | |||
IssueDto oldIssueDto = bulkChangeData.originalIssueByKey.get(issue.key()); | |||
Optional<UserDto> assignee = Optional.ofNullable(issue.assignee()).map(userDtoByUuid::get); | |||
return new ChangedIssue.Builder(issue.key()) | |||
.setNewStatus(issue.status()) | |||
.setNewIssueStatus(IssueStatus.of(issue.status(), issue.resolution())) | |||
.setNewIssueStatus(issue.getIssueStatus()) | |||
.setOldIssueStatus(oldIssueDto.getIssueStatus()) | |||
.setAssignee(assignee.map(u -> new User(u.getUuid(), u.getLogin(), u.getName())).orElse(null)) | |||
.setRule(new IssuesChangesNotificationBuilder.Rule(ruleDefinitionDto.getKey(), RuleType.valueOfNullable(ruleDefinitionDto.getType()), ruleDefinitionDto.getName())) | |||
.setProject(new Project.Builder(projectDto.uuid()) | |||
@@ -382,6 +383,7 @@ public class BulkChangeAction implements IssuesWsAction { | |||
private final Map<String, ComponentDto> componentsByUuid; | |||
private final Map<RuleKey, RuleDto> rulesByKey; | |||
private final List<Action> availableActions; | |||
private final Map<String, IssueDto> originalIssueByKey; | |||
BulkChangeData(DbSession dbSession, Request request) { | |||
this.sendNotification = request.mandatoryParamAsBoolean(PARAM_SEND_NOTIFICATIONS); | |||
@@ -398,12 +400,14 @@ public class BulkChangeAction implements IssuesWsAction { | |||
this.branchComponentByUuid = getAuthorizedComponents(allBranches).stream().collect(toMap(ComponentDto::uuid, identity())); | |||
this.branchesByProjectUuid = dbClient.branchDao().selectByUuids(dbSession, branchComponentByUuid.keySet()).stream() | |||
.collect(toMap(BranchDto::getUuid, identity())); | |||
this.issues = getAuthorizedIssues(allIssues); | |||
List<IssueDto> authorizedIssues = getAuthorizedIssues(allIssues); | |||
this.originalIssueByKey = authorizedIssues.stream().collect(toMap(IssueDto::getKee, identity())); | |||
this.issues = toDefaultIssues(authorizedIssues); | |||
this.componentsByUuid = getComponents(dbSession, | |||
issues.stream().map(DefaultIssue::componentUuid).collect(Collectors.toSet())).stream() | |||
.collect(toMap(ComponentDto::uuid, identity())); | |||
.collect(toMap(ComponentDto::uuid, identity())); | |||
this.rulesByKey = dbClient.ruleDao().selectByKeys(dbSession, | |||
issues.stream().map(DefaultIssue::ruleKey).collect(Collectors.toSet())).stream() | |||
issues.stream().map(DefaultIssue::ruleKey).collect(Collectors.toSet())).stream() | |||
.collect(toMap(RuleDto::getKey, identity())); | |||
this.availableActions = actions.stream() | |||
@@ -420,10 +424,15 @@ public class BulkChangeAction implements IssuesWsAction { | |||
return userSession.keepAuthorizedComponents(UserRole.USER, projectDtos); | |||
} | |||
private List<DefaultIssue> getAuthorizedIssues(List<IssueDto> allIssues) { | |||
private List<IssueDto> getAuthorizedIssues(List<IssueDto> allIssues) { | |||
Set<String> branchUuids = branchComponentByUuid.values().stream().map(ComponentDto::uuid).collect(Collectors.toSet()); | |||
return allIssues.stream() | |||
.filter(issue -> branchUuids.contains(issue.getProjectUuid())) | |||
.toList(); | |||
} | |||
private List<DefaultIssue> toDefaultIssues(List<IssueDto> allIssues) { | |||
return allIssues.stream() | |||
.map(IssueDto::toDefaultIssue) | |||
.toList(); | |||
} |
@@ -128,7 +128,7 @@ public class DoTransitionAction implements IssuesWsAction { | |||
transitionService.checkTransitionPermission(transitionKey, defaultIssue); | |||
if (transitionService.doTransition(defaultIssue, context, transitionKey)) { | |||
BranchDto branch = issueUpdater.getBranch(session, defaultIssue); | |||
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context, branch); | |||
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issueDto, defaultIssue, context, branch); | |||
if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(defaultIssue.projectUuid()) != null) { | |||
issueChangeEventService.distributeIssueChangeEvent(defaultIssue, null, null, transitionKey, branch, |
@@ -71,16 +71,17 @@ public class IssueUpdater { | |||
this.notificationSerializer = notificationSerializer; | |||
} | |||
public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context) { | |||
BranchDto branch = getBranch(dbSession, issue); | |||
return saveIssueAndPreloadSearchResponseData(dbSession, issue, context, branch); | |||
public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, IssueDto originalIssue, DefaultIssue newIssue, IssueChangeContext context) { | |||
BranchDto branch = getBranch(dbSession, newIssue); | |||
return saveIssueAndPreloadSearchResponseData(dbSession, originalIssue, newIssue, context, branch); | |||
} | |||
public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context, BranchDto branch) { | |||
Optional<RuleDto> rule = getRuleByKey(dbSession, issue.getRuleKey()); | |||
ComponentDto branchComponent = dbClient.componentDao().selectOrFailByUuid(dbSession, issue.projectUuid()); | |||
ComponentDto component = getComponent(dbSession, issue, issue.componentUuid()); | |||
IssueDto issueDto = doSaveIssue(dbSession, issue, context, rule.orElse(null), branchComponent, branch); | |||
public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, IssueDto originalIssue, | |||
DefaultIssue newIssue, IssueChangeContext context, BranchDto branch) { | |||
Optional<RuleDto> rule = getRuleByKey(dbSession, newIssue.getRuleKey()); | |||
ComponentDto branchComponent = dbClient.componentDao().selectOrFailByUuid(dbSession, newIssue.projectUuid()); | |||
ComponentDto component = getComponent(dbSession, newIssue, newIssue.componentUuid()); | |||
IssueDto issueDto = doSaveIssue(dbSession, originalIssue, newIssue, context, rule.orElse(null), branchComponent, branch); | |||
SearchResponseData result = new SearchResponseData(issueDto); | |||
rule.ifPresent(r -> result.addRules(singletonList(r))); | |||
@@ -107,18 +108,18 @@ public class IssueUpdater { | |||
return branchDto; | |||
} | |||
private IssueDto doSaveIssue(DbSession session, DefaultIssue issue, IssueChangeContext context, | |||
private IssueDto doSaveIssue(DbSession session, IssueDto originalIssue, DefaultIssue issue, IssueChangeContext context, | |||
@Nullable RuleDto ruleDto, ComponentDto project, BranchDto branchDto) { | |||
IssueDto issueDto = issueStorage.save(session, singletonList(issue)).iterator().next(); | |||
Date updateDate = issue.updateDate(); | |||
if ( | |||
// since this method is called after an update of the issue, date should never be null | |||
updateDate == null | |||
// name of rule is displayed in notification, rule must therefor be present | |||
|| ruleDto == null | |||
// notification are not supported on PRs | |||
|| !hasNotificationSupport(branchDto) | |||
|| context.getWebhookSource() != null) { | |||
// name of rule is displayed in notification, rule must therefor be present | |||
|| ruleDto == null | |||
// notification are not supported on PRs | |||
|| !hasNotificationSupport(branchDto) | |||
|| context.getWebhookSource() != null) { | |||
return issueDto; | |||
} | |||
@@ -131,6 +132,7 @@ public class IssueUpdater { | |||
new ChangedIssue.Builder(issue.key()) | |||
.setNewStatus(issue.status()) | |||
.setNewIssueStatus(IssueStatus.of(issue.status(), issue.resolution())) | |||
.setOldIssueStatus(originalIssue.getIssueStatus()) | |||
.setAssignee(assignee.map(assigneeDto -> new User(assigneeDto.getUuid(), assigneeDto.getLogin(), assigneeDto.getName())).orElse(null)) | |||
.setRule(new Rule(ruleDto.getKey(), RuleType.valueOfNullable(ruleDto.getType()), ruleDto.getName())) | |||
.setProject(new Project.Builder(project.uuid()) |
@@ -71,12 +71,12 @@ public class SetSeverityAction implements IssuesWsAction { | |||
public void define(WebService.NewController controller) { | |||
WebService.NewAction action = controller.createAction(ACTION_SET_SEVERITY) | |||
.setDescription("Change severity.<br/>" + | |||
"Requires the following permissions:" + | |||
"<ul>" + | |||
" <li>'Authentication'</li>" + | |||
" <li>'Browse' rights on project of the specified issue</li>" + | |||
" <li>'Administer Issues' rights on project of the specified issue</li>" + | |||
"</ul>") | |||
"Requires the following permissions:" + | |||
"<ul>" + | |||
" <li>'Authentication'</li>" + | |||
" <li>'Browse' rights on project of the specified issue</li>" + | |||
" <li>'Administer Issues' rights on project of the specified issue</li>" + | |||
"</ul>") | |||
.setSince("3.6") | |||
.setChangelog( | |||
new Change("10.4", "The response fields 'status' and 'resolution' are deprecated. Please use 'issueStatus' instead."), | |||
@@ -121,7 +121,7 @@ public class SetSeverityAction implements IssuesWsAction { | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), userSession.getUuid()).withRefreshMeasures().build(); | |||
if (issueFieldsSetter.setManualSeverity(issue, severity, context)) { | |||
BranchDto branch = issueUpdater.getBranch(session, issue); | |||
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, branch); | |||
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issueDto, issue, context, branch); | |||
if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(issue.projectUuid()) != null) { | |||
issueChangeEventService.distributeIssueChangeEvent(issue, severity, null, null, |
@@ -109,7 +109,7 @@ public class SetTagsAction implements IssuesWsAction { | |||
DefaultIssue issue = issueDto.toDefaultIssue(); | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), userSession.getUuid()).build(); | |||
if (issueFieldsSetter.setTags(issue, tags, context)) { | |||
return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context); | |||
return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issueDto, issue, context); | |||
} | |||
return new SearchResponseData(issueDto); | |||
} |
@@ -126,7 +126,7 @@ public class SetTypeAction implements IssuesWsAction { | |||
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(system2.now()), userSession.getUuid()).withRefreshMeasures().build(); | |||
if (issueFieldsSetter.setType(issue, ruleType, context)) { | |||
BranchDto branch = issueUpdater.getBranch(session, issue); | |||
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, branch); | |||
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issueDto, issue, context, branch); | |||
if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(issue.projectUuid()) != null) { | |||
issueChangeEventService.distributeIssueChangeEvent(issue, null, ruleType.name(), null, branch, | |||
response.getComponentByUuid(issue.projectUuid()).getKey()); |