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,
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;
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;
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;
}
@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());
.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;
}
}
@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) {
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();
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
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;
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;
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
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
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
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
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
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());
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
}
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);
}
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);
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() + "\"}");
+ }
}
}
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;
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());
@Before
public void setUp() {
ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
- context = new ActionContext(issue, issueChangeContext, project);
+ context = new ActionContext(issue, issueDto, issueChangeContext, project);
}
@Test
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;
private static final Date NOW = new Date(10_000_000_000L);
private static final String USER_LOGIN = "john";
-
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
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);
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()))
private static final Date NOW = new Date(10_000_000_000L);
private static final String USER_LOGIN = "john";
-
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
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();
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);
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;
@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);
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);
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);
.extracting(ComponentDto::uuid)
.containsExactlyInAnyOrder(issueDto.getComponentUuid());
}
+
@Test
void set_severity_whenSetImpactSeverity_shouldAlsoUpdateSeverity() {
IssueDto issueDto = issueDbTester.insertIssue(i -> i.setSeverity(MAJOR).setType(CODE_SMELL));
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);
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);
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
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();
return EnumSet.allOf(RuleType.class)
.stream()
.filter(ruleType -> SECURITY_HOTSPOT != ruleType)
- .map(t -> new Object[]{t})
+ .map(t -> new Object[] {t})
.toArray(Object[][]::new);
}
.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);
}
}
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;
public interface Context {
DefaultIssue issue();
+ IssueDto issueDto();
+
IssueChangeContext issueChangeContext();
ComponentDto project();
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;
}
return issue;
}
+ @Override
+ public IssueDto issueDto() {
+ return issueDto;
+ }
+
@Override
public IssueChangeContext issueChangeContext() {
return changeContext;
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;
@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
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_BULK_CHANGE)
.setDescription("Bulk change on issues. Up to 500 issues can be updated. <br/>" +
- "Requires authentication.")
+ "Requires authentication.")
.setSince("3.7")
.setChangelog(
new Change("10.4", ("Transitions '%s' and '%s' are now deprecated. Use transition '%s' instead. " +
.setExampleValue("security,java8");
action.createParam(PARAM_COMMENT)
.setDescription("Add a comment. "
- + "The comment will only be added to issues that are affected either by a change of type or a change of severity as a result of the same WS call.")
+ + "The comment will only be added to issues that are affected either by a change of type or a change of severity as a result of the same WS call.")
.setExampleValue("Here is my comment");
action.createParam(PARAM_SEND_NOTIFICATIONS)
.setSince("4.0")
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);
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()
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;
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;
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."),
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;
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;
}
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);
}
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;
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;
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;
public String getBranchName() {
return branchName;
}
+
+ public void addImpact(SoftwareQuality quality, Severity severity) {
+ impacts.put(quality, severity);
+ }
+
+ public Map<SoftwareQuality, Severity> getImpacts() {
+ return impacts;
+ }
}
public String getUserType() {
return userType;
}
-
}
*/
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;
@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);
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");