]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23287 add issue changed event and bulk action changes
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Fri, 8 Nov 2024 16:38:54 +0000 (17:38 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 11 Nov 2024 20:02:44 +0000 (20:02 +0000)
19 files changed:
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventService.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImpl.java
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/issues/IssueChangeEventServiceImplTest.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/AssignActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetSeverityActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/SetTypeActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetSeverityActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SetTypeActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/Action.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ActionContext.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/SetSeverityAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
sonar-core/src/main/java/org/sonar/core/util/issue/Issue.java
sonar-core/src/main/java/org/sonar/core/util/issue/IssueChangedEvent.java
sonar-core/src/test/java/org/sonar/core/util/issue/IssueChangedEventTest.java

index fa57e1b686744134ce9f690c1b30ac6673f30287..d90d03685699a9adf618428905a024023cbe3a05 100644 (file)
@@ -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<SoftwareQuality, Severity> impacts, @Nullable String type,
     @Nullable String transitionKey, BranchDto branch, String projectKey);
 
   void distributeIssueChangeEvent(Collection<DefaultIssue> issues, Map<String, ComponentDto> projectsByUuid,
index 8f5f6ebf4e6d24aad714a56d8b80bd0d00e56e9b..8255d802736e3b43566df4fb3f7e7447ac55e81e 100644 (file)
@@ -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<SoftwareQuality, Severity> 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<String, Issue> 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<DefaultIssue> issuesInProject, Issue[] issueChanges) {
+  private static IssueChangedEvent getIssueChangedEvent(String projectKey, Set<DefaultIssue> issuesInProject, Map<String, Issue> 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<DefaultIssue> issuesInProject, Map<String, Issue> issueChanges) {
+    for (DefaultIssue defaultIssue : issuesInProject) {
+      FieldDiffs currentChanges = defaultIssue.currentChange();
+      if (currentChanges == null) {
+        continue;
+      }
+
+      Map<String, Diff> 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
index 2889d00e22414597958ce8bd3ceff6356fa6abbd..6d28537363dbb0e9322dadc8b9f6f9a414c7f950 100644 (file)
@@ -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<DefaultIssue> 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<SoftwareQuality, Severity> impacts, @Nullable String type, @Nullable String transition, int page) {
+    underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, impacts, type, transition, branch, project.getKey());
 
     Deque<PushEventDto> 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<SoftwareQuality, Severity> 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<PushEventDto> 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<SoftwareQuality, Severity> impact : impacts.entrySet()) {
+      assertThat(payload).contains("\"impacts\":{\"");
+      assertThat(payload).contains(impact.getKey().name() + "\":\"" + impact.getValue().name() + "\"}");
+    }
   }
 
 }
index 8cf7b9c6a9e94cae3c9feb8e439c8290da0c1535..5bb806f7b1fedb9ba6c6223a3e0e77d363dc6060 100644 (file)
@@ -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
index be8303ae0a87a1d9beb9af70cc3d3e7ccc89a528..c1048119a6fdd06e6ab3ade5efacec6e27abb8bd 100644 (file)
@@ -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()))
index b4b901181c473a500cc7c925ee41b70d32304202..09ae8d18696794bcce6b92cb5d10cdea33b23f70 100644 (file)
@@ -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();
index c740e978cc50afd5d24df420d3d50de04450883c..4e557120ac20d04bb936ef76d0c40c35dca3ff19 100644 (file)
@@ -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);
index d8b532d796571867ba973c07efa7d713cb59a3ad..bfb5e53a138c2b2081a0038d4e86140bf1e34b0b 100644 (file)
@@ -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
index 80be6b3d1a1663448e18ac69e4984e5f752623a5..fd3c1a782a6fcf34626c5b37f4169b5885e91cef 100644 (file)
@@ -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);
   }
 }
index 1042dc6178170b4181a436c883fb74bd8aee1572..0980245dd628e6776f6ae8517ba57bc847843dcb 100644 (file)
@@ -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();
index 4432173c778d454f7421ba1ef440b5f614a9c100..0f9ef8c7db680291ac7dac42d628722bdcb9d5a5 100644 (file)
@@ -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;
index 4f65a8e3ad439088596481a90a299ae819a4fcf3..b3b4215b6f2d6abce7861e7deb5e837128f8b498 100644 (file)
@@ -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<String, Object> 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<SoftwareQuality, org.sonar.api.issue.impact.Severity> effectiveImpacts) {
+    if (issue.impacts().isEmpty()) {
+      issue.replaceImpacts(effectiveImpacts);
+      issue.setChanged(true);
+    }
   }
 
   @Override
index 8e61d80bf9c98e6120f26d8ca18672faff328fca..08d673ba893700da76fcfb9cc539cfefa1b0cb20 100644 (file)
@@ -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. <br/>" +
-                      "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<DefaultIssue> 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()
index 13c43f018fda3b6d1782a1963e971cf214debb24..9fc2b63e97c7bf0073197a9248537bf7484c36e2 100644 (file)
@@ -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;
index f973d24a00c2791c43ee77103e44ae0dea18e99a..83fea43fa584447f728a7d58ed32babcab982fd7 100644 (file)
@@ -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.<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.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<SoftwareQuality, org.sonar.api.issue.impact.Severity> effectiveImpacts) {
-    if(issue.impacts().isEmpty()){
+    if (issue.impacts().isEmpty()) {
       issue.replaceImpacts(effectiveImpacts);
       issue.setChanged(true);
     }
index 126c3550e94597bc1cad08f8f66cfae14162ce5f..ba6bf3430efd80f19118d52c51b9d391b27eb2d9 100644 (file)
@@ -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;
index eb551f5e02a791260f6f2613f32df0224e67915b..73c44873ae3ede3e56abc5b461fb4d43ce27b7fb 100644 (file)
 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<SoftwareQuality, Severity> 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<SoftwareQuality, Severity> getImpacts() {
+    return impacts;
+  }
 }
index b340986a0688c41de36b2e1a641d8ed4c1fed697..d36d4f2f9c38792f5b5984a8608a8e2f4e7cc159 100644 (file)
@@ -73,5 +73,4 @@ public class IssueChangedEvent implements Serializable {
   public String getUserType() {
     return userType;
   }
-
 }
index 9e1093e7ebe08467752f8e1ea910dc17bf0152a1..c8c3649c16e3053e9b1baf532b5d8e1ca2168e8e 100644 (file)
  */
 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");