.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()) {
.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));
});
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");
return ofNullable(newIssueStatus);
}
+ public Optional<IssueStatus> getOldIssueStatus() {
+ return ofNullable(oldIssueStatus);
+ }
+
public Optional<User> getAssignee() {
return ofNullable(assignee);
}
private final String key;
@CheckForNull
private IssueStatus newIssueStatus;
+ @CheckForNull
+ private IssueStatus oldIssueStatus;
private String newStatus;
@CheckForNull
private User assignee;
return this;
}
+ public Builder setOldIssueStatus(@Nullable IssueStatus oldIssueStatus) {
+ this.oldIssueStatus = oldIssueStatus;
+ return this;
+ }
+
public Builder setRule(Rule rule) {
this.rule = rule;
return this;
.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))
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());
}
.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))
@CheckForNull
private final String newIssueStatus;
@CheckForNull
+ private final String oldIssueStatus;
+ @CheckForNull
private final User assignee;
private final RuleKey ruleKey;
private final String projectUuid;
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
return this;
}
+ public Builder setOldIssueStatus(@Nullable String oldIssueStatus) {
+ this.oldIssueStatus = oldIssueStatus;
+ return this;
+ }
+
public Builder setAssignee(@Nullable User assignee) {
this.assignee = assignee;
return this;
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;
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;
@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());
@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);
@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);
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);
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);
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);
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"));
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(
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"));
public static Object[][] oneOrMoreProjectCounts() {
return new Object[][] {
{1},
- {2 + new Random().nextInt(3)},
+ {5},
};
}
};
}
- 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)))
verify(issueFieldsSetter).addComment(defaultIssueCaptor.capture(), eq(comment), eq(issueChangeContext));
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
+ any(IssueDto.class),
defaultIssueCaptor.capture(),
eq(issueChangeContext));
verify(issueFieldsSetter).assign(defaultIssueCaptor.capture(), userMatcher(assignee), any(IssueChangeContext.class));
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
+ any(IssueDto.class),
defaultIssueCaptor.capture(),
any(IssueChangeContext.class));
if (transitionDone) {
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
+ any(IssueDto.class),
defaultIssueCaptor.capture(),
eq(issueChangeContext));
if (transitionDone) {
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
+ any(IssueDto.class),
defaultIssueCaptor.capture(),
eq(issueChangeContext));
verify(issueFieldsSetter).addComment(defaultIssueCaptor.capture(), eq(comment), eq(issueChangeContext));
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
+ any(IssueDto.class),
defaultIssueCaptor.capture(),
eq(issueChangeContext));
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;
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));
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));
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;
@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);
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();
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();
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));
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();
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);
}
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);
}
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();
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());
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)
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();
}
}
}
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())) {
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())) {
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);
}
}
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);
}
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;
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. " +
.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")
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())
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);
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()
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();
}
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,
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)));
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;
}
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())
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."),
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,
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);
}
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());