From 60d28c06f872cfa30bd2399a977dfbc02063079d Mon Sep 17 00:00:00 2001 From: =?utf8?q?L=C3=A9o=20Geoffroy?= Date: Fri, 8 Nov 2024 17:38:54 +0100 Subject: [PATCH] SONAR-23287 add issue changed event and bulk action changes --- .../issues/IssueChangeEventService.java | 4 +- .../issues/IssueChangeEventServiceImpl.java | 43 ++++++++++--- .../IssueChangeEventServiceImplTest.java | 62 +++++++++++++------ .../sonar/server/issue/AssignActionIT.java | 4 +- .../server/issue/SetSeverityActionIT.java | 36 ++++++++++- .../sonar/server/issue/SetTypeActionIT.java | 3 +- .../server/issue/ws/DoTransitionActionIT.java | 2 +- .../server/issue/ws/SetSeverityActionIT.java | 17 ++--- .../server/issue/ws/SetTypeActionIT.java | 6 +- .../java/org/sonar/server/issue/Action.java | 3 + .../org/sonar/server/issue/ActionContext.java | 10 ++- .../sonar/server/issue/SetSeverityAction.java | 23 ++++++- .../server/issue/ws/BulkChangeAction.java | 11 ++-- .../server/issue/ws/DoTransitionAction.java | 3 +- .../server/issue/ws/SetSeverityAction.java | 25 ++++---- .../sonar/server/issue/ws/SetTypeAction.java | 3 +- .../java/org/sonar/core/util/issue/Issue.java | 17 ++++- .../core/util/issue/IssueChangedEvent.java | 1 - .../util/issue/IssueChangedEventTest.java | 14 ++++- 19 files changed, 216 insertions(+), 71 deletions(-) diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventService.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventService.java index fa57e1b6867..d90d0368569 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventService.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventService.java @@ -22,12 +22,14 @@ package org.sonar.server.pushapi.issues; import java.util.Collection; import java.util.Map; import javax.annotation.Nullable; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.core.issue.DefaultIssue; import org.sonar.db.component.BranchDto; import org.sonar.db.component.ComponentDto; public interface IssueChangeEventService { - void distributeIssueChangeEvent(DefaultIssue issue, @Nullable String severity, @Nullable String type, + void distributeIssueChangeEvent(DefaultIssue issue, @Nullable String severity, Map impacts, @Nullable String type, @Nullable String transitionKey, BranchDto branch, String projectKey); void distributeIssueChangeEvent(Collection issues, Map projectsByUuid, diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java index 8f5f6ebf4e6..8255d802736 100644 --- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java +++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java @@ -21,6 +21,7 @@ package org.sonar.server.pushapi.issues; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import java.io.Serializable; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; @@ -28,8 +29,11 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.server.ServerSide; import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.FieldDiffs; import org.sonar.core.issue.FieldDiffs.Diff; import org.sonar.core.util.issue.Issue; import org.sonar.core.util.issue.IssueChangedEvent; @@ -58,6 +62,7 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { private static final String RESOLUTION_KEY = "resolution"; private static final String SEVERITY_KEY = "severity"; + private static final String IMPACT_SEVERITY_KEY = "impactSeverity"; private static final String TYPE_KEY = "type"; private final DbClient dbClient; @@ -67,17 +72,18 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { } @Override - public void distributeIssueChangeEvent(DefaultIssue issue, @Nullable String severity, @Nullable String type, @Nullable String transition, + public void distributeIssueChangeEvent(DefaultIssue issue, @Nullable String severity, Map impacts, @Nullable String type, @Nullable String transition, BranchDto branch, String projectKey) { Issue changedIssue = new Issue(issue.key(), branch.getKey()); Boolean resolved = isResolved(transition); - if (severity == null && type == null && resolved == null) { + if (severity == null && type == null && resolved == null && impacts.isEmpty()) { return; } - IssueChangedEvent event = new IssueChangedEvent(projectKey, new Issue[]{changedIssue}, + impacts.forEach(changedIssue::addImpact); + IssueChangedEvent event = new IssueChangedEvent(projectKey, new Issue[] {changedIssue}, resolved, severity, type); persistEvent(event, branch.getProjectUuid()); @@ -95,12 +101,12 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { .filter(i -> i.projectUuid().equals(entry.getKey())) .collect(Collectors.toSet()); - Issue[] issueChanges = issuesInProject.stream() + Map issueChanges = issuesInProject.stream() .filter(i -> branchesByProjectUuid.get(i.projectUuid()).getBranchType().equals(BRANCH)) .map(i -> new Issue(i.key(), branchesByProjectUuid.get(i.projectUuid()).getKey())) - .toArray(Issue[]::new); + .collect(Collectors.toMap(Issue::getIssueKey, i -> i)); - if (issueChanges.length == 0) { + if (issueChanges.isEmpty()) { continue; } @@ -114,7 +120,7 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { } @CheckForNull - private static IssueChangedEvent getIssueChangedEvent(String projectKey, Set issuesInProject, Issue[] issueChanges) { + private static IssueChangedEvent getIssueChangedEvent(String projectKey, Set issuesInProject, Map issueChanges) { DefaultIssue firstIssue = issuesInProject.stream().iterator().next(); if (firstIssue.currentChange() == null) { @@ -137,6 +143,7 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { severity = diffs.get(SEVERITY_KEY).newValue() == null ? null : diffs.get(SEVERITY_KEY).newValue().toString(); isRelevantEvent = true; } + addImpactsToChangeEvent(issuesInProject, issueChanges); if (diffs.containsKey(TYPE_KEY)) { type = diffs.get(TYPE_KEY).newValue() == null ? null : diffs.get(TYPE_KEY).newValue().toString(); @@ -147,7 +154,27 @@ public class IssueChangeEventServiceImpl implements IssueChangeEventService { return null; } - return new IssueChangedEvent(projectKey, issueChanges, resolved, severity, type); + return new IssueChangedEvent(projectKey, issueChanges.values().toArray(new Issue[0]), resolved, severity, type); + } + + private static void addImpactsToChangeEvent(Set issuesInProject, Map issueChanges) { + for (DefaultIssue defaultIssue : issuesInProject) { + FieldDiffs currentChanges = defaultIssue.currentChange(); + if (currentChanges == null) { + continue; + } + + Map diffs = currentChanges.diffs(); + if (issueChanges.containsKey(defaultIssue.key()) + && diffs.containsKey(IMPACT_SEVERITY_KEY)) { + Serializable newValue = diffs.get(IMPACT_SEVERITY_KEY).newValue(); + String impact = newValue != null ? newValue.toString() : null; + Issue issue = issueChanges.get(defaultIssue.key()); + if (impact != null) { + issue.addImpact(SoftwareQuality.valueOf(impact.split(":")[0]), Severity.valueOf(impact.split(":")[1])); + } + } + } } @CheckForNull diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java index 2889d00e224..6d28537363d 100644 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java +++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java @@ -22,12 +22,15 @@ package org.sonar.server.pushapi.issues; import java.nio.charset.StandardCharsets; import java.util.Deque; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rules.RuleType; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.FieldDiffs; @@ -36,6 +39,7 @@ import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ProjectData; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.pushevent.PushEventDto; @@ -70,7 +74,18 @@ public class IssueChangeEventServiceImplTest { RuleDto rule = db.rules().insert(); IssueDto issue = db.issues().insert(rule, projectData.getMainBranchDto(), projectData.getMainBranchComponent(), i -> i.setSeverity(MAJOR.name())); - assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, BLOCKER.name(), null, null, null, 1); + assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, BLOCKER.name(), Map.of(), null, null, null, 1); + } + + @Test + public void distributeIssueChangeEvent_whenSingleIssueChange_shouldChangeImpacts() { + ProjectData projectData = db.components().insertPublicProject(); + RuleDto rule = db.rules().insert(); + IssueDto issue = db.issues().insert(rule, projectData.getMainBranchDto(), projectData.getMainBranchComponent(), + i -> i.replaceAllImpacts(List.of(new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH), + new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.MEDIUM)))); + + assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, null, Map.of(SoftwareQuality.SECURITY, Severity.HIGH), null, null, null, 1); } @Test @@ -79,7 +94,7 @@ public class IssueChangeEventServiceImplTest { RuleDto rule = db.rules().insert(); IssueDto issue = db.issues().insert(rule, projectData.getMainBranchDto(), projectData.getMainBranchComponent(), i -> i.setSeverity(MAJOR.name())); - assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, null, Common.RuleType.BUG.name(), null, null, 1); + assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, null, Map.of(), Common.RuleType.BUG.name(), null, null, 1); } @Test @@ -90,15 +105,15 @@ public class IssueChangeEventServiceImplTest { RuleDto rule = db.rules().insert(); IssueDto issue = db.issues().insert(rule, mainBranch, projectData.getMainBranchComponent(), i -> i.setSeverity(MAJOR.name())); - assertPushEventIsPersisted(project, mainBranch, issue, null, null, ACCEPT, true, 1); - assertPushEventIsPersisted(project, mainBranch, issue, null, null, WONT_FIX, true, 2); - assertPushEventIsPersisted(project, mainBranch, issue, null, null, REOPEN, false, 3); - assertPushEventIsPersisted(project, mainBranch, issue, null, null, FALSE_POSITIVE, true, 4); - assertPushEventIsPersisted(project, mainBranch, issue, null, null, REOPEN, false, 5); - assertPushEventIsPersisted(project, mainBranch, issue, null, null, RESOLVE, false, 6); - assertPushEventIsPersisted(project, mainBranch, issue, null, null, REOPEN, false, 7); - assertNoIssueDistribution(project, mainBranch, issue, null, null, CONFIRM, 8); - assertNoIssueDistribution(project, mainBranch, issue, null, null, UNCONFIRM, 9); + assertPushEventIsPersisted(project, mainBranch, issue, null, Map.of(), null, ACCEPT, true, 1); + assertPushEventIsPersisted(project, mainBranch, issue, null, Map.of(), null, WONT_FIX, true, 2); + assertPushEventIsPersisted(project, mainBranch, issue, null, Map.of(), null, REOPEN, false, 3); + assertPushEventIsPersisted(project, mainBranch, issue, null, Map.of(), null, FALSE_POSITIVE, true, 4); + assertPushEventIsPersisted(project, mainBranch, issue, null, Map.of(), null, REOPEN, false, 5); + assertPushEventIsPersisted(project, mainBranch, issue, null, Map.of(), null, RESOLVE, false, 6); + assertPushEventIsPersisted(project, mainBranch, issue, null, Map.of(), null, REOPEN, false, 7); + assertNoIssueDistribution(project, mainBranch, issue, null, Map.of(), null, CONFIRM, 8); + assertNoIssueDistribution(project, mainBranch, issue, null, Map.of(), null, UNCONFIRM, 9); } @Test @@ -108,7 +123,7 @@ public class IssueChangeEventServiceImplTest { ComponentDto branchComponent = db.components().insertFile(featureBranch); RuleDto rule = db.rules().insert(); IssueDto issue = db.issues().insert(rule, featureBranch, branchComponent, i -> i.setSeverity(MAJOR.name())); - assertPushEventIsPersisted(projectData.getProjectDto(), featureBranch, issue, BLOCKER.name(), null, null, null, 1); + assertPushEventIsPersisted(projectData.getProjectDto(), featureBranch, issue, BLOCKER.name(), Map.of(), null, null, null, 1); } @Test @@ -117,7 +132,7 @@ public class IssueChangeEventServiceImplTest { RuleDto rule = db.rules().insert(); IssueDto issue = db.issues().insert(rule, projectData.getMainBranchDto(), projectData.getMainBranchComponent(), i -> i.setSeverity(MAJOR.name())); - assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, BLOCKER.name(), Common.RuleType.BUG.name(), ACCEPT, true, 1); + assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, BLOCKER.name(), Map.of(), Common.RuleType.BUG.name(), ACCEPT, true, 1); } @Test @@ -145,10 +160,12 @@ public class IssueChangeEventServiceImplTest { DefaultIssue defaultIssue1 = issue1.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs() .setDiff("resolution", null, null) .setDiff("severity", MAJOR.name(), CRITICAL.name()) + .setDiff("impactSeverity", "RELIABILITY:HIGH", "RELIABILITY:MEDIUM") .setDiff("type", RuleType.BUG.name(), CODE_SMELL.name())); DefaultIssue defaultIssue2 = issue2.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs() .setDiff("resolution", "OPEN", "FALSE-POSITIVE") .setDiff("severity", MAJOR.name(), CRITICAL.name()) + .setDiff("impactSeverity", "MAINTAINABILITY:HIGH", "MAINTAINABILITY:LOW") .setDiff("type", RuleType.BUG.name(), CODE_SMELL.name())); Set issues = Set.of(defaultIssue1, defaultIssue2, issue3.toDefaultIssue()); @@ -185,13 +202,16 @@ public class IssueChangeEventServiceImplTest { assertThat(firstPayload) .contains("\"userSeverity\":\"" + CRITICAL.name() + "\"", "\"userType\":\"" + CODE_SMELL.name() + "\"", - "\"resolved\":" + false); + "\"resolved\":" + false) + .contains("\"issues\":[{\"issueKey\":\"%s\",\"branchName\":\"main\",\"impacts\":{\"RELIABILITY\":\"MEDIUM\"}}]".formatted(issue1.getKee())); String secondPayload = new String(project2Event.get().getPayload(), StandardCharsets.UTF_8); assertThat(secondPayload) .contains("\"userSeverity\":\"" + CRITICAL.name() + "\"", "\"userType\":\"" + CODE_SMELL.name() + "\"", - "\"resolved\":" + true); + "\"resolved\":" + true) + .contains("\"issues\":[{\"issueKey\":\"%s\",\"branchName\":\"main\",\"impacts\":{\"MAINTAINABILITY\":\"LOW\"}}]".formatted(issue2.getKee())); + } @Test @@ -225,8 +245,8 @@ public class IssueChangeEventServiceImplTest { } private void assertNoIssueDistribution(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity, - @Nullable String type, @Nullable String transition, int page) { - underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, type, transition, branch, project.getKey()); + Map impacts, @Nullable String type, @Nullable String transition, int page) { + underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, impacts, type, transition, branch, project.getKey()); Deque events = db.getDbClient().pushEventDao() .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, page); @@ -234,8 +254,9 @@ public class IssueChangeEventServiceImplTest { } private void assertPushEventIsPersisted(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity, + Map impacts, @Nullable String type, @Nullable String transition, Boolean resolved, int page) { - underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, type, transition, branch, project.getKey()); + underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, impacts, type, transition, branch, project.getKey()); Deque events = db.getDbClient().pushEventDao() .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, page); @@ -255,6 +276,11 @@ public class IssueChangeEventServiceImplTest { if (resolved != null) { assertThat(payload).contains("\"resolved\":" + resolved); } + + for (Map.Entry impact : impacts.entrySet()) { + assertThat(payload).contains("\"impacts\":{\""); + assertThat(payload).contains(impact.getKey().name() + "\":\"" + impact.getValue().name() + "\"}"); + } } } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/AssignActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/AssignActionIT.java index 8cf7b9c6a9e..5bb806f7b1f 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/AssignActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/AssignActionIT.java @@ -32,6 +32,7 @@ import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.IssueChangeContext; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; +import org.sonar.db.issue.IssueDto; import org.sonar.db.user.UserDto; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.tester.UserSessionRule; @@ -55,6 +56,7 @@ public class AssignActionIT { private final IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(new Date(), "user_uuid").build(); private final DefaultIssue issue = new DefaultIssue().setKey("ABC").setAssigneeUuid(ISSUE_CURRENT_ASSIGNEE_UUID); + private final IssueDto issueDto = new IssueDto().setKee("ABC").setAssigneeUuid(ISSUE_CURRENT_ASSIGNEE_UUID); private Action.Context context; private final AssignAction underTest = new AssignAction(db.getDbClient(), new IssueFieldsSetter()); @@ -62,7 +64,7 @@ public class AssignActionIT { @Before public void setUp() { ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent(); - context = new ActionContext(issue, issueChangeContext, project); + context = new ActionContext(issue, issueDto, issueChangeContext, project); } @Test diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetSeverityActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetSeverityActionIT.java index be8303ae0a8..c1048119a6f 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetSeverityActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetSeverityActionIT.java @@ -22,16 +22,21 @@ package org.sonar.server.issue; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import java.util.Date; +import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Rule; import org.junit.Test; import org.sonar.api.issue.Issue; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rules.RuleType; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.FieldDiffs; import org.sonar.db.DbTester; import org.sonar.db.component.BranchDto; import org.sonar.db.component.ComponentDto; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.issue.IssueTesting; import org.sonar.db.project.ProjectDto; @@ -54,7 +59,6 @@ public class SetSeverityActionIT { private static final Date NOW = new Date(10_000_000_000L); private static final String USER_LOGIN = "john"; - @Rule public UserSessionRule userSession = UserSessionRule.standalone(); @@ -70,7 +74,7 @@ public class SetSeverityActionIT { IssueDto issueDto = newIssue().setSeverity(MAJOR); DefaultIssue issue = issueDto.toDefaultIssue(); setUserWithBrowseAndAdministerIssuePermission(issueDto); - Action.Context context = new ActionContext(issue, issueChangeContextByUserBuilder(NOW, userSession.getUuid()).build(), null); + Action.Context context = new ActionContext(issue, issueDto, issueChangeContextByUserBuilder(NOW, userSession.getUuid()).build(), null); action.execute(ImmutableMap.of("severity", MINOR), context); @@ -84,6 +88,34 @@ public class SetSeverityActionIT { assertThat(change.get("severity").oldValue()).isEqualTo(MAJOR); } + @Test + public void set_severity_whenImpactsIsMissing_shouldCreateImpactsAndImpactSeverityAreUpdated() { + IssueDto issueDto = newIssue().setSeverity(MAJOR).replaceAllImpacts(List.of()) + .setRuleDefaultImpacts(Set.of(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH))); + DefaultIssue issue = issueDto.toDefaultIssue(); + assertThat(issue.impacts()).isEmpty(); + + setUserWithBrowseAndAdministerIssuePermission(issueDto); + Action.Context context = new ActionContext(issue, issueDto, issueChangeContextByUserBuilder(NOW, userSession.getUuid()).build(), null); + + action.execute(ImmutableMap.of("severity", MINOR), context); + + assertThat(issue.impacts()).containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.LOW); + } + + @Test + public void set_severity_whenSeverityHasChanged_shouldUpdateMatchingImpactSeverity() { + IssueDto issueDto = newIssue().setSeverity(MAJOR).replaceAllImpacts(Set.of(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH))); + DefaultIssue issue = issueDto.toDefaultIssue(); + + setUserWithBrowseAndAdministerIssuePermission(issueDto); + Action.Context context = new ActionContext(issue, issueDto, issueChangeContextByUserBuilder(NOW, userSession.getUuid()).build(), null); + + action.execute(ImmutableMap.of("severity", MINOR), context); + + assertThat(issue.impacts()).containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.LOW); + } + @Test public void fail_if_parameter_not_found() { assertThatThrownBy(() -> action.verify(ImmutableMap.of("unknwown", MINOR), Lists.newArrayList(), new AnonymousMockUserSession())) diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetTypeActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetTypeActionIT.java index b4b901181c4..09ae8d18696 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetTypeActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetTypeActionIT.java @@ -52,7 +52,6 @@ public class SetTypeActionIT { private static final Date NOW = new Date(10_000_000_000L); private static final String USER_LOGIN = "john"; - @Rule public UserSessionRule userSession = UserSessionRule.standalone(); @@ -70,7 +69,7 @@ public class SetTypeActionIT { setUserWithBrowseAndAdministerIssuePermission(issueDto); action.execute(ImmutableMap.of("type", VULNERABILITY.name()), - new ActionContext(issue, issueChangeContextByUserBuilder(NOW, userSession.getUuid()).build(), null)); + new ActionContext(issue, issueDto, issueChangeContextByUserBuilder(NOW, userSession.getUuid()).build(), null)); assertThat(issue.type()).isEqualTo(VULNERABILITY); assertThat(issue.isChanged()).isTrue(); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java index c740e978cc5..4e557120ac2 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java @@ -127,7 +127,7 @@ public class DoTransitionActionIT { verify(responseWriter).write(eq(issue.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class), eq(true)); verifyContentOfPreloadedSearchResponseData(issue); - verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any()); + verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any(), any()); IssueDto issueReloaded = db.getDbClient().issueDao().selectByKey(db.getSession(), issue.getKey()).get(); assertThat(issueReloaded.getStatus()).isEqualTo(STATUS_CONFIRMED); assertThat(issueChangePostProcessor.calledComponents()).containsExactlyInAnyOrder(file); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetSeverityActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetSeverityActionIT.java index d8b532d7965..bfb5e53a138 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetSeverityActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetSeverityActionIT.java @@ -20,6 +20,7 @@ package org.sonar.server.issue.ws; import java.util.List; +import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; @@ -112,14 +113,15 @@ class SetSeverityActionIT { @Test void set_severity_whenSetSeverity_shouldAlsoUpdateImpactSeverity() { - IssueDto issueDto = issueDbTester.insertIssue(i -> i.setSeverity(MAJOR).setType(CODE_SMELL)); + IssueDto issueDto = issueDbTester.insertIssue(i -> i.setSeverity(MAJOR).setType(CODE_SMELL) + .replaceAllImpacts(List.of(new ImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM, false)))); setUserWithBrowseAndAdministerIssuePermission(issueDto); call(issueDto.getKey(), MINOR, null); verify(responseWriter).write(eq(issueDto.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class), eq(true)); verifyContentOfPreloadedSearchResponseData(issueDto); - verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any()); + verify(issueChangeEventService).distributeIssueChangeEvent(any(), eq(MINOR), eq(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW)), any(), any(), any(), any()); IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get(); assertThat(issueReloaded.getSeverity()).isEqualTo(MINOR); @@ -145,7 +147,7 @@ class SetSeverityActionIT { verify(responseWriter).write(eq(issueDto.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class), eq(true)); verifyContentOfPreloadedSearchResponseData(issueDto); - verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any()); + verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any(), any()); IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get(); assertThat(issueReloaded.getSeverity()).isEqualTo(MINOR); @@ -169,7 +171,7 @@ class SetSeverityActionIT { verify(responseWriter).write(eq(issueDto.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class), eq(true)); verifyContentOfPreloadedSearchResponseData(issueDto); - verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any()); + verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any(), any()); IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get(); assertThat(issueReloaded.getSeverity()).isEqualTo(MINOR); @@ -184,6 +186,7 @@ class SetSeverityActionIT { .extracting(ComponentDto::uuid) .containsExactlyInAnyOrder(issueDto.getComponentUuid()); } + @Test void set_severity_whenSetImpactSeverity_shouldAlsoUpdateSeverity() { IssueDto issueDto = issueDbTester.insertIssue(i -> i.setSeverity(MAJOR).setType(CODE_SMELL)); @@ -194,7 +197,7 @@ class SetSeverityActionIT { verify(responseWriter).write(eq(issueDto.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class), eq(true)); verifyContentOfPreloadedSearchResponseData(issueDto); - verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any()); + verify(issueChangeEventService).distributeIssueChangeEvent(any(), eq(MINOR), eq(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW)), any(), any(), any(), any()); IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get(); assertThat(issueReloaded.getSeverity()).isEqualTo(MINOR); @@ -220,7 +223,7 @@ class SetSeverityActionIT { verify(responseWriter).write(eq(issueDto.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class), eq(true)); verifyContentOfPreloadedSearchResponseData(issueDto); - verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any()); + verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any(), any()); IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get(); assertThat(issueReloaded.getSeverity()).isEqualTo(MAJOR); @@ -247,7 +250,7 @@ class SetSeverityActionIT { verify(responseWriter).write(eq(issueDto.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class), eq(true)); - verify(issueChangeEventService, times(0)).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any()); + verify(issueChangeEventService, times(0)).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any(), any()); } @Test diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetTypeActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetTypeActionIT.java index 80be6b3d1a1..fd3c1a782a6 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetTypeActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetTypeActionIT.java @@ -136,7 +136,7 @@ public class SetTypeActionIT { assertThat(issueChangePostProcessor.calledComponents()) .extracting(ComponentDto::uuid) .containsExactlyInAnyOrder(issueDto.getComponentUuid()); - verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any()); + verify(issueChangeEventService).distributeIssueChangeEvent(any(), any(), any(), any(), any(), any(), any()); } else { assertThat(issueChangePostProcessor.wasCalled()) .isFalse(); @@ -304,7 +304,7 @@ public class SetTypeActionIT { return EnumSet.allOf(RuleType.class) .stream() .filter(ruleType -> SECURITY_HOTSPOT != ruleType) - .map(t -> new Object[]{t}) + .map(t -> new Object[] {t}) .toArray(Object[][]::new); } @@ -316,7 +316,7 @@ public class SetTypeActionIT { .collect(Collectors.toSet()); return Sets.cartesianProduct(set, set) .stream() - .map(ruleTypes -> new Object[]{ruleTypes.get(0), ruleTypes.get(1)}) + .map(ruleTypes -> new Object[] {ruleTypes.get(0), ruleTypes.get(1)}) .toArray(Object[][]::new); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/Action.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/Action.java index 1042dc61781..0980245dd62 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/Action.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/Action.java @@ -29,6 +29,7 @@ import org.sonar.api.server.ServerSide; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.IssueChangeContext; import org.sonar.db.component.ComponentDto; +import org.sonar.db.issue.IssueDto; import org.sonar.server.issue.workflow.Condition; import org.sonar.server.user.UserSession; @@ -80,6 +81,8 @@ public abstract class Action { public interface Context { DefaultIssue issue(); + IssueDto issueDto(); + IssueChangeContext issueChangeContext(); ComponentDto project(); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ActionContext.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ActionContext.java index 4432173c778..0f9ef8c7db6 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ActionContext.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ActionContext.java @@ -22,14 +22,17 @@ package org.sonar.server.issue; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.IssueChangeContext; import org.sonar.db.component.ComponentDto; +import org.sonar.db.issue.IssueDto; public class ActionContext implements Action.Context { private final DefaultIssue issue; + private final IssueDto issueDto; private final IssueChangeContext changeContext; private final ComponentDto project; - public ActionContext(DefaultIssue issue, IssueChangeContext changeContext, ComponentDto project) { + public ActionContext(DefaultIssue issue, IssueDto issueDto, IssueChangeContext changeContext, ComponentDto project) { this.issue = issue; + this.issueDto = issueDto; this.changeContext = changeContext; this.project = project; } @@ -39,6 +42,11 @@ public class ActionContext implements Action.Context { return issue; } + @Override + public IssueDto issueDto() { + return issueDto; + } + @Override public IssueChangeContext issueChangeContext() { return changeContext; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/SetSeverityAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/SetSeverityAction.java index 4f65a8e3ad4..b3b4215b6f2 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/SetSeverityAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/SetSeverityAction.java @@ -22,10 +22,13 @@ package org.sonar.server.issue; import java.util.Collection; import java.util.Map; import org.sonar.api.issue.Issue; -import org.sonar.server.issue.workflow.IsUnResolved; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rules.RuleType; import org.sonar.api.server.ServerSide; +import org.sonar.api.server.rule.internal.ImpactMapper; import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.rule.ImpactSeverityMapper; +import org.sonar.server.issue.workflow.IsUnResolved; import org.sonar.server.user.UserSession; import static com.google.common.base.Preconditions.checkArgument; @@ -61,7 +64,23 @@ public class SetSeverityAction extends Action { @Override public boolean execute(Map properties, Context context) { - return issueUpdater.setManualSeverity(context.issue(), verifySeverityParameter(properties), context.issueChangeContext()); + String severity = verifySeverityParameter(properties); + boolean updated = issueUpdater.setManualSeverity(context.issue(), severity, context.issueChangeContext()); + + SoftwareQuality softwareQuality = ImpactMapper.convertToSoftwareQuality(context.issue().type()); + if (updated + && context.issueDto().getEffectiveImpacts().containsKey(softwareQuality)) { + createImpactsIfMissing(context.issue(), context.issueDto().getEffectiveImpacts()); + issueUpdater.setImpactManualSeverity(context.issue(), softwareQuality, ImpactSeverityMapper.mapImpactSeverity(severity), context.issueChangeContext()); + } + return updated; + } + + private static void createImpactsIfMissing(DefaultIssue issue, Map effectiveImpacts) { + if (issue.impacts().isEmpty()) { + issue.replaceImpacts(effectiveImpacts); + issue.setChanged(true); + } } @Override diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java index 8e61d80bf9c..08d673ba893 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java @@ -151,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.
" + - "Requires authentication.") + "Requires authentication.") .setSince("3.7") .setChangelog( new Change("10.4", ("Transitions '%s' and '%s' are now deprecated. Use transition '%s' instead. " + @@ -195,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") @@ -252,7 +252,8 @@ public class BulkChangeAction implements IssuesWsAction { private static Predicate bulkChange(IssueChangeContext issueChangeContext, BulkChangeData bulkChangeData, BulkChangeResult result) { return issue -> { - ActionContext actionContext = new ActionContext(issue, issueChangeContext, bulkChangeData.branchComponentByUuid.get(issue.projectUuid())); + ActionContext actionContext = new ActionContext(issue, bulkChangeData.originalIssueByKey.get(issue.key()), issueChangeContext, + bulkChangeData.branchComponentByUuid.get(issue.projectUuid())); bulkChangeData.getActionsWithoutComment().forEach(applyAction(actionContext, bulkChangeData, result)); addCommentIfNeeded(actionContext, bulkChangeData); return result.success.contains(issue); @@ -405,9 +406,9 @@ public class BulkChangeAction implements IssuesWsAction { 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() diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java index 13c43f018fd..9fc2b63e97c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java @@ -21,6 +21,7 @@ package org.sonar.server.issue.ws; import com.google.common.io.Resources; import java.util.Date; +import java.util.Map; import org.sonar.api.issue.DefaultTransitions; import org.sonar.api.issue.impact.Severity; import org.sonar.api.server.ws.Change; @@ -134,7 +135,7 @@ public class DoTransitionAction implements IssuesWsAction { 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, + issueChangeEventService.distributeIssueChangeEvent(defaultIssue, null, Map.of(), null, transitionKey, branch, response.getComponentByUuid(defaultIssue.projectUuid()).getKey()); } return response; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java index f973d24a00c..83fea43fa58 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java @@ -81,12 +81,12 @@ public class SetSeverityAction implements IssuesWsAction { public void define(WebService.NewController controller) { WebService.NewAction action = controller.createAction(ACTION_SET_SEVERITY) .setDescription("Change severity.
" + - "Requires the following permissions:" + - "
    " + - "
  • 'Authentication'
  • " + - "
  • 'Browse' rights on project of the specified issue
  • " + - "
  • 'Administer Issues' rights on project of the specified issue
  • " + - "
") + "Requires the following permissions:" + + "
    " + + "
  • 'Authentication'
  • " + + "
  • 'Browse' rights on project of the specified issue
  • " + + "
  • 'Administer Issues' rights on project of the specified issue
  • " + + "
") .setSince("3.6") .setChangelog( new Change("10.8", "Add 'impact' parameter to the request."), @@ -161,15 +161,15 @@ public class SetSeverityAction implements IssuesWsAction { createImpactsIfMissing(issue, effectiveImpacts); if (issueFieldsSetter.setImpactManualSeverity(issue, softwareQuality, manualImpactSeverity, context)) { String manualSeverity = null; - boolean severityUpdated = false; + boolean severityHasChanged = false; if (convertToRuleType(softwareQuality).equals(issue.type())) { manualSeverity = convertToDeprecatedSeverity(manualImpactSeverity); - severityUpdated = issueFieldsSetter.setManualSeverity(issue, manualSeverity, context); + severityHasChanged = issueFieldsSetter.setManualSeverity(issue, manualSeverity, context); } BranchDto branch = issueUpdater.getBranch(session, issue); SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issueDto, issue, context, branch); if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(issue.projectUuid()) != null) { - issueChangeEventService.distributeIssueChangeEvent(issue, severityUpdated ? manualSeverity : null, null, null, + issueChangeEventService.distributeIssueChangeEvent(issue, severityHasChanged ? manualSeverity : null, Map.of(softwareQuality, manualImpactSeverity), null, null, branch, getProjectKey(issue, response)); } return response; @@ -181,14 +181,15 @@ public class SetSeverityAction implements IssuesWsAction { IssueChangeContext context) { if (issueFieldsSetter.setManualSeverity(issue, severity, context)) { SoftwareQuality softwareQuality = convertToSoftwareQuality(issue.type()); + boolean impactHasChanged = false; if (issueDto.getEffectiveImpacts().containsKey(softwareQuality)) { createImpactsIfMissing(issue, issueDto.getEffectiveImpacts()); - issueFieldsSetter.setImpactManualSeverity(issue, softwareQuality, mapImpactSeverity(severity), context); + impactHasChanged = issueFieldsSetter.setImpactManualSeverity(issue, softwareQuality, mapImpactSeverity(severity), context); } BranchDto branch = issueUpdater.getBranch(session, issue); 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, + issueChangeEventService.distributeIssueChangeEvent(issue, severity, impactHasChanged ? Map.of(softwareQuality, mapImpactSeverity(severity)) : Map.of(), null, null, branch, getProjectKey(issue, response)); } return response; @@ -205,7 +206,7 @@ public class SetSeverityAction implements IssuesWsAction { } private static void createImpactsIfMissing(DefaultIssue issue, Map effectiveImpacts) { - if(issue.impacts().isEmpty()){ + if (issue.impacts().isEmpty()) { issue.replaceImpacts(effectiveImpacts); issue.setChanged(true); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java index 126c3550e94..ba6bf3430ef 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java @@ -22,6 +22,7 @@ package org.sonar.server.issue.ws; import com.google.common.io.Resources; import java.util.Date; import java.util.EnumSet; +import java.util.Map; import org.sonar.api.rules.RuleType; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; @@ -128,7 +129,7 @@ public class SetTypeAction implements IssuesWsAction { BranchDto branch = issueUpdater.getBranch(session, issue); 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, + issueChangeEventService.distributeIssueChangeEvent(issue, null, Map.of(), ruleType.name(), null, branch, response.getComponentByUuid(issue.projectUuid()).getKey()); } return response; diff --git a/sonar-core/src/main/java/org/sonar/core/util/issue/Issue.java b/sonar-core/src/main/java/org/sonar/core/util/issue/Issue.java index eb551f5e02a..73c44873ae3 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/issue/Issue.java +++ b/sonar-core/src/main/java/org/sonar/core/util/issue/Issue.java @@ -20,10 +20,15 @@ package org.sonar.core.util.issue; import java.io.Serializable; +import java.util.EnumMap; +import java.util.Map; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; public class Issue implements Serializable { - private String issueKey; - private String branchName; + private final String issueKey; + private final String branchName; + private final Map impacts = new EnumMap<>(SoftwareQuality.class); public Issue(String issueKey, String branchName) { this.issueKey = issueKey; @@ -37,4 +42,12 @@ public class Issue implements Serializable { public String getBranchName() { return branchName; } + + public void addImpact(SoftwareQuality quality, Severity severity) { + impacts.put(quality, severity); + } + + public Map getImpacts() { + return impacts; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java b/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java index b340986a068..d36d4f2f9c3 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java +++ b/sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java @@ -73,5 +73,4 @@ public class IssueChangedEvent implements Serializable { public String getUserType() { return userType; } - } diff --git a/sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java b/sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java index 9e1093e7ebe..c8c3649c16e 100644 --- a/sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java +++ b/sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java @@ -19,7 +19,10 @@ */ package org.sonar.core.util.issue; +import java.util.Arrays; import org.junit.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -31,8 +34,10 @@ public class IssueChangedEventTest { @Test public void issueChangedEvent_instantiation_accepts_nulls() { - Issue[] issues = new Issue[]{new Issue(ISSUE_KEY, BRANCH_NAME)}; - IssueChangedEvent event = new IssueChangedEvent(PROJECT_KEY, issues, null, null, null); + Issue issue = new Issue(ISSUE_KEY, BRANCH_NAME); + issue.addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH); + + IssueChangedEvent event = new IssueChangedEvent(PROJECT_KEY, new Issue[] {issue}, null, null, null); assertThat(event.getEvent()).isEqualTo("IssueChanged"); assertThat(event.getProjectKey()).isEqualTo(PROJECT_KEY); @@ -40,11 +45,14 @@ public class IssueChangedEventTest { assertThat(event.getUserSeverity()).isNull(); assertThat(event.getUserType()).isNull(); assertThat(event.getIssues()).hasSize(1); + assertThat(Arrays.stream(event.getIssues()).iterator().next().getImpacts()) + .hasSize(1) + .containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.HIGH); } @Test public void issueChangedEvent_instantiation_accepts_actual_values() { - Issue[] issues = new Issue[]{new Issue(ISSUE_KEY, BRANCH_NAME)}; + Issue[] issues = new Issue[] {new Issue(ISSUE_KEY, BRANCH_NAME)}; IssueChangedEvent event = new IssueChangedEvent(PROJECT_KEY, issues, true, "BLOCKER", "BUG"); assertThat(event.getEvent()).isEqualTo("IssueChanged"); -- 2.39.5