import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.ArgumentCaptor;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.AnticipatedTransitionDto;
import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDao;
import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.IssueMapper;
import org.sonar.db.newcodeperiod.NewCodePeriodType;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleTesting;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
import static org.assertj.core.data.MapEntry.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
underTest.execute(context);
ArgumentCaptor<IssueDto> issueDtoCaptor = ArgumentCaptor.forClass(IssueDto.class);
- verify(conflictResolver).resolve(eq(defaultIssue), issueDtoCaptor.capture(), any(IssueMapper.class));
+ verify(conflictResolver).resolve(eq(defaultIssue), issueDtoCaptor.capture(), any(IssueDao.class), any(DbSession.class));
assertThat(issueDtoCaptor.getValue().getKey()).isEqualTo(issue.getKey());
assertThat(context.getStatistics().getAll()).contains(
entry("inserts", "0"), entry("updates", "1"), entry("merged", "1"));
.setCreationDate(new Date(NOW))
.setNew(true)
.setIsOnChangedLine(true)
+ .addImpact(SoftwareQuality.SECURITY, Severity.MEDIUM)
.setType(RuleType.BUG)).close();
TestComputationStepContext context = new TestComputationStepContext();
assertThat(result.getSeverity()).isEqualTo(BLOCKER);
assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+ assertThat(result.getImpacts()).extracting(i -> i.getSoftwareQuality(), i -> i.getSeverity())
+ .containsExactly(tuple(SoftwareQuality.SECURITY, Severity.MEDIUM));
assertThat(context.getStatistics().getAll()).contains(
entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
assertThat(result.isNewCodeReferenceIssue()).isTrue();
updater.setPastGap(raw, base.gap(), changeContext);
updater.setPastEffort(raw, base.effort(), changeContext);
updater.setCodeVariants(raw, requireNonNull(base.codeVariants()), changeContext);
+ updater.setImpacts(raw, base.impacts(), changeContext);
}
public void doAutomaticTransition(DefaultIssue issue) {
*/
package org.sonar.ce.task.projectanalysis.issue;
+import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleType;
String getSeverity();
Set<String> getSecurityStandards();
+
+ Map<SoftwareQuality, Severity> getDefaultImpacts();
}
package org.sonar.ce.task.projectanalysis.issue;
import com.google.common.base.MoreObjects;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleType;
private final String defaultRuleDescription;
private final String severity;
private final Set<String> securityStandards;
+ private final Map<SoftwareQuality, Severity> defaultImpacts;
public RuleImpl(RuleDto dto) {
this.uuid = dto.getUuid();
this.defaultRuleDescription = getNonNullDefaultRuleDescription(dto);
this.severity = Optional.ofNullable(dto.getSeverityString()).orElse(dto.getAdHocSeverity());
this.securityStandards = dto.getSecurityStandards();
+ this.defaultImpacts = dto.getDefaultImpacts().stream()
+ .collect(Collectors.toMap(i -> i.getSoftwareQuality(), i -> i.getSeverity()));
}
private static String getNonNullDefaultRuleDescription(RuleDto dto) {
return securityStandards;
}
+ @Override
+ public Map<SoftwareQuality, Severity> getDefaultImpacts() {
+ return defaultImpacts;
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
import java.util.Set;
import java.util.function.Supplier;
import javax.annotation.CheckForNull;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleType;
public Set<String> getSecurityStandards() {
return Collections.emptySet();
}
+
+ @Override
+ public Map<SoftwareQuality, Severity> getDefaultImpacts() {
+ //TODO external issues
+ return Collections.emptyMap();
+ }
}
}
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
-import org.slf4j.LoggerFactory;
import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
issue.setQuickFixAvailable(reportIssue.getQuickFixAvailable());
issue.setRuleDescriptionContextKey(reportIssue.hasRuleDescriptionContextKey() ? reportIssue.getRuleDescriptionContextKey() : null);
issue.setCodeVariants(reportIssue.getCodeVariantsList());
+
+ issue.replaceImpacts(convertImpacts(issue.ruleKey(), reportIssue.getOverridenImpactsList()));
return issue;
}
}
}
+ private Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> convertImpacts(RuleKey ruleKey, List<ScannerReport.Impact> overridenImpactsList) {
+ if (overridenImpactsList.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ Rule rule = ruleRepository.getByKey(ruleKey);
+ return overridenImpactsList.stream()
+ .filter(i -> rule.getDefaultImpacts().containsKey(SoftwareQuality.valueOf(i.getSoftwareQuality())))
+ .collect(Collectors.toMap(i -> SoftwareQuality.valueOf(i.getSoftwareQuality()), i -> org.sonar.api.issue.impact.Severity.valueOf(i.getSeverity())));
+ }
+
private static DbIssues.MessageFormattings convertMessageFormattings(List<ScannerReport.MessageFormatting> msgFormattings) {
DbIssues.MessageFormattings.Builder builder = DbIssues.MessageFormattings.newBuilder();
msgFormattings.stream()
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbSession;
+import org.sonar.db.issue.IssueDao;
import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.IssueMapper;
/**
* Support concurrent modifications on issues made by analysis and users at the same time
private static final Logger LOG = LoggerFactory.getLogger(UpdateConflictResolver.class);
- public void resolve(DefaultIssue issue, IssueDto dbIssue, IssueMapper mapper) {
+ public void resolve(DefaultIssue issue, IssueDto dbIssue, IssueDao issueDao, DbSession dbSession) {
LOG.debug("Resolve conflict on issue {}", issue.key());
mergeFields(dbIssue, issue);
- mapper.update(IssueDto.toDtoForUpdate(issue, System.currentTimeMillis()));
+ issueDao.update(dbSession, IssueDto.toDtoForUpdate(issue, System.currentTimeMillis()));
}
@VisibleForTesting
import org.sonar.db.DbSession;
import org.sonar.db.issue.AnticipatedTransitionMapper;
import org.sonar.db.issue.IssueChangeMapper;
+import org.sonar.db.issue.IssueDao;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.issue.IssueMapper;
import org.sonar.db.issue.NewCodeReferenceIssueDto;
List<DefaultIssue> updatedIssues = new ArrayList<>(ISSUE_BATCHING_SIZE);
List<DefaultIssue> noLongerNewIssues = new ArrayList<>(ISSUE_BATCHING_SIZE);
List<DefaultIssue> newCodeIssuesToMigrate = new ArrayList<>(ISSUE_BATCHING_SIZE);
-
- IssueMapper mapper = dbSession.getMapper(IssueMapper.class);
+ IssueDao issueDao = dbClient.issueDao();
IssueChangeMapper changeMapper = dbSession.getMapper(IssueChangeMapper.class);
AnticipatedTransitionMapper anticipatedTransitionMapper = dbSession.getMapper(AnticipatedTransitionMapper.class);
while (issues.hasNext()) {
if (issue.isNew() || issue.isCopied()) {
addedIssues.add(issue);
if (addedIssues.size() >= ISSUE_BATCHING_SIZE) {
- persistNewIssues(statistics, addedIssues, mapper, changeMapper, anticipatedTransitionMapper);
+ persistNewIssues(statistics, addedIssues, issueDao, changeMapper, anticipatedTransitionMapper, dbSession);
addedIssues.clear();
}
} else if (issue.isChanged()) {
updatedIssues.add(issue);
if (updatedIssues.size() >= ISSUE_BATCHING_SIZE) {
- persistUpdatedIssues(statistics, updatedIssues, mapper, changeMapper);
+ persistUpdatedIssues(statistics, updatedIssues, issueDao, changeMapper, dbSession);
updatedIssues.clear();
}
} else if (isOnBranchUsingReferenceBranch() && issue.isNoLongerNewCodeReferenceIssue()) {
noLongerNewIssues.add(issue);
if (noLongerNewIssues.size() >= ISSUE_BATCHING_SIZE) {
- persistNoLongerNewIssues(statistics, noLongerNewIssues, mapper);
+ persistNoLongerNewIssues(statistics, noLongerNewIssues, issueDao, dbSession);
noLongerNewIssues.clear();
}
} else if (isOnBranchUsingReferenceBranch() && issue.isToBeMigratedAsNewCodeReferenceIssue()) {
newCodeIssuesToMigrate.add(issue);
if (newCodeIssuesToMigrate.size() >= ISSUE_BATCHING_SIZE) {
- persistNewCodeIssuesToMigrate(statistics, newCodeIssuesToMigrate, mapper);
+ persistNewCodeIssuesToMigrate(statistics, newCodeIssuesToMigrate, issueDao, dbSession);
newCodeIssuesToMigrate.clear();
}
}
}
- persistNewIssues(statistics, addedIssues, mapper, changeMapper, anticipatedTransitionMapper);
- persistUpdatedIssues(statistics, updatedIssues, mapper, changeMapper);
- persistNoLongerNewIssues(statistics, noLongerNewIssues, mapper);
- persistNewCodeIssuesToMigrate(statistics, newCodeIssuesToMigrate, mapper);
+
+ persistNewIssues(statistics, addedIssues, issueDao, changeMapper, anticipatedTransitionMapper, dbSession);
+ persistUpdatedIssues(statistics, updatedIssues, issueDao, changeMapper, dbSession);
+ persistNoLongerNewIssues(statistics, noLongerNewIssues, issueDao, dbSession);
+ persistNewCodeIssuesToMigrate(statistics, newCodeIssuesToMigrate, issueDao, dbSession);
flushSession(dbSession);
} finally {
statistics.dumpTo(context);
}
private void persistNewIssues(IssueStatistics statistics, List<DefaultIssue> addedIssues,
- IssueMapper mapper, IssueChangeMapper changeMapper, AnticipatedTransitionMapper anticipatedTransitionMapper) {
+ IssueDao issueDao, IssueChangeMapper changeMapper, AnticipatedTransitionMapper anticipatedTransitionMapper, DbSession dbSession) {
final long now = system2.now();
addedIssues.forEach(addedIssue -> {
String ruleUuid = ruleRepository.getByKey(addedIssue.ruleKey()).getUuid();
IssueDto dto = IssueDto.toDtoForComputationInsert(addedIssue, ruleUuid, now);
- mapper.insert(dto);
+ issueDao.insert(dbSession, dto);
if (isOnBranchUsingReferenceBranch() && addedIssue.isOnChangedLine()) {
- mapper.insertAsNewCodeOnReferenceBranch(NewCodeReferenceIssueDto.fromIssueDto(dto, now, uuidFactory));
+ issueDao.insertAsNewCodeOnReferenceBranch(dbSession, NewCodeReferenceIssueDto.fromIssueDto(dto, now, uuidFactory));
}
statistics.inserts++;
issueStorage.insertChanges(changeMapper, addedIssue, uuidFactory);
});
}
- private void persistUpdatedIssues(IssueStatistics statistics, List<DefaultIssue> updatedIssues, IssueMapper mapper, IssueChangeMapper changeMapper) {
+ private void persistUpdatedIssues(IssueStatistics statistics, List<DefaultIssue> updatedIssues, IssueDao
+ issueDao, IssueChangeMapper changeMapper, DbSession dbSession) {
if (updatedIssues.isEmpty()) {
return;
}
long now = system2.now();
updatedIssues.forEach(i -> {
IssueDto dto = IssueDto.toDtoForUpdate(i, now);
- mapper.updateIfBeforeSelectedDate(dto);
+ issueDao.updateIfBeforeSelectedDate(dbSession, dto);
statistics.updates++;
});
// retrieve those of the updatedIssues which have not been updated and apply conflictResolver on them
List<String> updatedIssueKeys = updatedIssues.stream().map(DefaultIssue::key).toList();
- List<IssueDto> conflictIssueKeys = mapper.selectByKeysIfNotUpdatedAt(updatedIssueKeys, now);
+ List<IssueDto> conflictIssueKeys = issueDao.selectByKeysIfNotUpdatedAt(dbSession, updatedIssueKeys, now);
if (!conflictIssueKeys.isEmpty()) {
Map<String, DefaultIssue> issuesByKeys = updatedIssues.stream().collect(Collectors.toMap(DefaultIssue::key, Function.identity()));
conflictIssueKeys
.forEach(dbIssue -> {
DefaultIssue updatedIssue = issuesByKeys.get(dbIssue.getKey());
- conflictResolver.resolve(updatedIssue, dbIssue, mapper);
+ conflictResolver.resolve(updatedIssue, dbIssue, issueDao, dbSession);
statistics.merged++;
});
}
updatedIssues.forEach(i -> issueStorage.insertChanges(changeMapper, i, uuidFactory));
}
- private static void persistNoLongerNewIssues(IssueStatistics statistics, List<DefaultIssue> noLongerNewIssues, IssueMapper mapper) {
+ private static void persistNoLongerNewIssues(IssueStatistics
+ statistics, List<DefaultIssue> noLongerNewIssues, IssueDao issueDao, DbSession dbSession) {
if (noLongerNewIssues.isEmpty()) {
return;
}
noLongerNewIssues.forEach(i -> {
- mapper.deleteAsNewCodeOnReferenceBranch(i.key());
+ issueDao.deleteAsNewCodeOnReferenceBranch(dbSession, i.key());
statistics.updates++;
});
}
- private void persistNewCodeIssuesToMigrate(IssueStatistics statistics, List<DefaultIssue> newCodeIssuesToMigrate, IssueMapper mapper) {
+ private void persistNewCodeIssuesToMigrate(IssueStatistics
+ statistics, List<DefaultIssue> newCodeIssuesToMigrate, IssueDao issueDao, DbSession dbSession) {
if (newCodeIssuesToMigrate.isEmpty()) {
return;
}
long now = system2.now();
newCodeIssuesToMigrate.forEach(i -> {
- mapper.insertAsNewCodeOnReferenceBranch(NewCodeReferenceIssueDto.fromIssueKey(i.key(), now, uuidFactory));
+ issueDao.insertAsNewCodeOnReferenceBranch(dbSession, NewCodeReferenceIssueDto.fromIssueKey(i.key(), now, uuidFactory));
statistics.updates++;
});
}
import java.util.Date;
import java.util.Map;
import javax.annotation.CheckForNull;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
defaultIssue.setAnticipatedTransitionUuid(next.getAnticipatedTransitionUuid());
}
+ for (IssueCache.Impact impact : next.getImpactsList()) {
+ defaultIssue.addImpact(SoftwareQuality.valueOf(impact.getSoftwareQuality()), Severity.valueOf(impact.getSeverity()));
+ }
for (IssueCache.FieldDiffs protoFieldDiffs : next.getChangesList()) {
defaultIssue.addChange(toDefaultIssueChanges(protoFieldDiffs));
}
-
return defaultIssue;
}
builder.setIsNoLongerNewCodeReferenceIssue(defaultIssue.isNoLongerNewCodeReferenceIssue());
defaultIssue.getAnticipatedTransitionUuid().ifPresent(builder::setAnticipatedTransitionUuid);
+
+ for (Map.Entry<SoftwareQuality, Severity> impact : defaultIssue.impacts().entrySet()) {
+ builder.addImpacts(IssueCache.Impact.newBuilder()
+ .setSoftwareQuality(impact.getKey().name())
+ .setSeverity(impact.getValue().name())
+ .build());
+ }
for (FieldDiffs fieldDiffs : defaultIssue.changes()) {
builder.addChanges(toProtoIssueChanges(fieldDiffs));
}
optional string codeVariants = 46;
optional string assigneeLogin = 47;
optional string anticipatedTransitionUuid = 48;
+ repeated Impact impacts = 49;
}
message Comment {
optional string oldValue = 1;
optional string newValue = 2;
}
+
+message Impact {
+ required string software_quality = 1;
+ required string severity = 2;
+}
*/
package org.sonar.ce.task.projectanalysis.issue;
+import java.util.EnumMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
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.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleType;
private String pluginKey;
private boolean isExternal;
private boolean isAdHoc;
- private Set<String> securityStandards = new HashSet<>();
+ private final Set<String> securityStandards = new HashSet<>();
+ private final Map<SoftwareQuality, Severity> defaultImpacts = new EnumMap<>(SoftwareQuality.class);
public DumbRule(RuleKey key) {
this.key = key;
return securityStandards;
}
+ @Override
+ public Map<SoftwareQuality, Severity> getDefaultImpacts() {
+ return defaultImpacts;
+ }
+
@Override
public boolean isExternal() {
return isExternal;
return this;
}
+ public DumbRule addDefaultImpact(SoftwareQuality softwareQuality, Severity severity) {
+ defaultImpacts.put(softwareQuality, severity);
+ return this;
+ }
+
}
import java.util.Set;
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.api.utils.Duration;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
.setRuleKey(XOO_X1)
.setRuleDescriptionContextKey("spring")
.setCodeVariants(Set.of("foo", "bar"))
+ .addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
.setCreationDate(parseDate("2015-10-01"))
.setUpdateDate(parseDate("2015-10-02"))
.setCloseDate(parseDate("2015-10-03"));
.setGap(15d)
.setRuleDescriptionContextKey("hibernate")
.setCodeVariants(Set.of("donut"))
+ .addImpact(SoftwareQuality.RELIABILITY, Severity.LOW)
.setEffort(Duration.create(15L))
.setManualSeverity(false)
.setLocations(issueLocations)
.containsOnly(entry("foo", new FieldDiffs.Diff<>("bar", "donut")));
assertThat(raw.changes().get(1).diffs())
.containsOnly(entry("file", new FieldDiffs.Diff<>("A", "B")));
-
+ assertThat(raw.impacts())
+ .containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
verify(updater).setPastSeverity(raw, BLOCKER, issueChangeContext);
verify(updater).setPastLine(raw, 10);
verify(updater).setRuleDescriptionContextKey(raw, "hibernate");
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Collection;
import java.util.Collections;
+import java.util.Map;
import java.util.Optional;
+import java.util.function.Consumer;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
public void load_issues_from_report() {
RuleKey ruleKey = RuleKey.of("java", "S001");
markRuleAsActive(ruleKey);
-
+ registerRule(ruleKey, "name", r -> r.addDefaultImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(newTextRange(2))
.setMsg("the message")
.addMsgFormatting(ScannerReport.MessageFormatting.newBuilder().setStart(0).setEnd(3).setType(CODE).build())
+ .addOverridenImpacts(ScannerReport.Impact.newBuilder()
+ .setSoftwareQuality(SoftwareQuality.MAINTAINABILITY.name())
+ .setSeverity(org.sonar.api.issue.impact.Severity.HIGH.name())
+ .build())
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
.setSeverity(Constants.Severity.BLOCKER)
assertInitializedIssue(issue);
assertThat(issue.effort()).isNull();
assertThat(issue.getRuleDescriptionContextKey()).isEmpty();
+ assertThat(issue.impacts()).containsExactlyEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH));
}
@Test
.containsOnly(Optional.of(TEST_CONTEXT_KEY));
}
+ @Test
+ public void create_whenImpactIsNotDefinedAtRuleLevel_shouldDiscardImpacts() {
+ RuleKey ruleKey = RuleKey.of("java", "S001");
+ markRuleAsActive(ruleKey);
+ registerRule(ruleKey, "name", r -> r.addDefaultImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW));
+ ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+ .setTextRange(newTextRange(2))
+ .setMsg("the message")
+ .setRuleRepository(ruleKey.repository())
+ .setRuleKey(ruleKey.rule())
+ .addOverridenImpacts(ScannerReport.Impact.newBuilder()
+ .setSoftwareQuality(SoftwareQuality.SECURITY.name())
+ .setSeverity(org.sonar.api.issue.impact.Severity.LOW.name()).build())
+ .build();
+ reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
+ Input<DefaultIssue> input = underTest.create(FILE);
+
+ Collection<DefaultIssue> issues = input.getIssues();
+ assertThat(issues)
+ .hasSize(1);
+ assertThat(issues.iterator().next().impacts()).isEmpty();
+ }
+
@Test
public void set_rule_name_as_message_when_issue_message_from_report_is_empty() {
RuleKey ruleKey = RuleKey.of("java", "S001");
markRuleAsActive(ruleKey);
- registerRule(ruleKey, "Rule 1");
+ registerRule(ruleKey, "Rule 1", r -> {
+ });
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
@DataProvider
public static Object[][] ruleTypeAndStatusByIssueType() {
- return new Object[][] {
+ return new Object[][]{
{IssueType.CODE_SMELL, RuleType.CODE_SMELL, STATUS_OPEN},
{IssueType.BUG, RuleType.BUG, STATUS_OPEN},
{IssueType.VULNERABILITY, RuleType.VULNERABILITY, STATUS_OPEN},
activeRulesHolder.put(new ActiveRule(ruleKey, Severity.CRITICAL, emptyMap(), 1_000L, null, "qp1"));
}
- private void registerRule(RuleKey ruleKey, String name) {
+ private void registerRule(RuleKey ruleKey, String name, Consumer<DumbRule> dumbRulePopulator) {
DumbRule dumbRule = new DumbRule(ruleKey);
dumbRule.setName(name);
+ dumbRulePopulator.accept(dumbRule);
ruleRepository.add(dumbRule);
}
}
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.DateUtils;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbSession;
+import org.sonar.db.issue.IssueDao;
import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.IssueMapper;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
.setStatus(STATUS_OPEN);
// Issue as seen and changed by end-user
- IssueMapper mapper = mock(IssueMapper.class);
+ IssueDao issueDao = mock(IssueDao.class);
+ DbSession dbSession = mock(DbSession.class);
IssueDto issueDto = new IssueDto()
.setKee("ABCDE")
.setType(CODE_SMELL)
// field changed by user
.setAssigneeUuid("arthur-uuid");
- new UpdateConflictResolver().resolve(issue, issueDto, mapper);
+ new UpdateConflictResolver().resolve(issue, issueDto, issueDao, dbSession);
ArgumentCaptor<IssueDto> argument = ArgumentCaptor.forClass(IssueDto.class);
- verify(mapper).update(argument.capture());
+ verify(issueDao).update(any(), argument.capture());
IssueDto updatedIssue = argument.getValue();
assertThat(updatedIssue.getKee()).isEqualTo("ABCDE");
assertThat(updatedIssue.getAssigneeUuid()).isEqualTo("arthur-uuid");
package org.sonar.ce.task.projectanalysis.util.cache;
import java.util.Date;
+import java.util.Map;
import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
assertThat(defaultIssue.getRuleDescriptionContextKey()).isEmpty();
}
+ @Test
+ public void toDefaultIssue_whenImpactIsSet_shouldSetItInDefaultIssue() {
+ IssueCache.Issue issue = prepareIssueWithCompulsoryFields()
+ .addImpacts(toImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH))
+ .addImpacts(toImpact(SoftwareQuality.RELIABILITY, Severity.LOW))
+ .build();
+
+ DefaultIssue defaultIssue = ProtobufIssueDiskCache.toDefaultIssue(issue);
+
+ assertThat(defaultIssue.impacts()).containsExactlyInAnyOrderEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH, SoftwareQuality.RELIABILITY, Severity.LOW));
+ }
+
@Test
public void toProto_whenRuleDescriptionContextKeySet_shouldCopyToIssueProto() {
DefaultIssue defaultIssue = createDefaultIssueWithMandatoryFields();
assertThat(issue.hasRuleDescriptionContextKey()).isFalse();
}
+ @Test
+ public void toProto_whenRuleDescriptionContextKeyIsSet_shouldCopyToIssueProto() {
+ DefaultIssue defaultIssue = createDefaultIssueWithMandatoryFields();
+ defaultIssue.addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
+ defaultIssue.addImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
+
+ IssueCache.Issue issue = ProtobufIssueDiskCache.toProto(IssueCache.Issue.newBuilder(), defaultIssue);
+
+ assertThat(issue.getImpactsList()).containsExactly(
+ toImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
+ toImpact(SoftwareQuality.RELIABILITY, Severity.LOW)
+ );
+ }
+
+ private IssueCache.Impact toImpact(SoftwareQuality softwareQuality, Severity severity) {
+ return IssueCache.Impact.newBuilder().setSoftwareQuality(softwareQuality.name()).setSeverity(severity.name()).build();
+ }
+
private static DefaultIssue createDefaultIssueWithMandatoryFields() {
DefaultIssue defaultIssue = new DefaultIssue();
defaultIssue.setKey("test_issue:key");
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.core.util.Uuids;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.Pagination;
assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
}
+ @Test
+ public void updateIfBeforeSelectedDate_whenCalledWithBeforeSelectDate_shouldUpdateImpacts() {
+ prepareTables();
+ IssueDto issueDto = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1)
+ .setSelectedAt(1_440_000_000_000L)
+ .replaceAllImpacts(List.of(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.LOW)));
+
+ underTest.updateIfBeforeSelectedDate(db.getSession(), issueDto);
+
+ assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).getImpacts()).extracting(i -> i.getSoftwareQuality(), i -> i.getSeverity())
+ .containsExactly(tuple(SoftwareQuality.RELIABILITY, Severity.LOW));
+
+ }
+
+ @Test
+ public void updateIfBeforeSelectedDate_whenCalledWithAfterSelectDate_shouldNotUpdateImpacts() {
+ prepareTables();
+ IssueDto issueDto = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1)
+ .setSelectedAt(1_400_000_000_000L)
+ .replaceAllImpacts(List.of(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.LOW)));
+
+ underTest.updateIfBeforeSelectedDate(db.getSession(), issueDto);
+
+ assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).getImpacts()).extracting(i -> i.getSoftwareQuality(), i -> i.getSeverity())
+ .containsExactlyInAnyOrder(tuple(SoftwareQuality.RELIABILITY, Severity.MEDIUM), tuple(SoftwareQuality.SECURITY, Severity.LOW));
+ }
+
private static IssueDto createIssueWithKey(String issueKey) {
return createIssueWithKey(issueKey, PROJECT_UUID, FILE_UUID);
}
.setCodeVariants(Set.of("variant2", "variant3"));
// selected after last update -> ok
- dto.setSelectedAt(1500000000000L);
+ dto.setSelectedAt(1_500_000_000_000L);
int count = underTest.updateIfBeforeSelectedDate(dto);
assertThat(count).isOne();
.setCodeVariants(Set.of("variant2", "variant3"));
// selected before last update -> ko
- dto.setSelectedAt(1400000000000L);
+ dto.setSelectedAt(1_400_000_000_000L);
int count = underTest.updateIfBeforeSelectedDate(dto);
assertThat(count).isZero();
IntStream.range(0, count).forEach(i -> {
IssueDto issue = dbTester.issues().insertIssue(t -> t.setRule(rule).setProject(projectOrView).setComponent(projectOrView)
.addImpact(new ImpactDto().setUuid(UuidFactoryFast.getInstance().create())
- .setSoftwareQuality(SoftwareQuality.MAINTAINABILITY)
+ .setSoftwareQuality(SoftwareQuality.SECURITY)
.setSeverity(Severity.HIGH)));
issueKeys.add("'" + issue.getKey() + "'");
});
ComponentDto dir = db.components().insertComponent(newDirectory(mainBranch, "path"));
ComponentDto file = db.components().insertComponent(newFileDto(mainBranch, dir));
- IssueDto issue1 = db.issues().insert(rule, mainBranch, file);
+ IssueDto issue1 = db.issues().insert(rule, mainBranch, file, issue -> issue.replaceAllImpacts(Collections.emptyList()));
IssueDto orphanIssue = db.issues().insert(rule, mainBranch, file, issue -> issue.setComponentUuid("nonExisting"));
IssueChangeDto orphanIssueChange = db.issues().insertChange(orphanIssue);
db.issues().insertNewCodeReferenceIssue(orphanIssue);
private static void updateIssueImpacts(IssueDto issueDto, IssueMapper mapper) {
mapper.deleteIssueImpacts(issueDto.getKey());
- insertInsertIssueImpacts(issueDto, mapper);
+ insertIssueImpact(issueDto, mapper);
}
- private static void insertInsertIssueImpacts(IssueDto issueDto, IssueMapper mapper) {
+ private static void insertIssueImpact(IssueDto issueDto, IssueMapper mapper) {
issueDto.getImpacts()
.forEach(impact -> mapper.insertIssueImpact(issueDto.getKey(), impact));
}
public void update(DbSession session, IssueDto dto) {
mapper(session).update(dto);
+ updateIssueImpacts(dto, mapper(session));
+ }
+
+ public void updateIfBeforeSelectedDate(DbSession session, IssueDto dto) {
+ int updatedRows = mapper(session).updateIfBeforeSelectedDate(dto);
+ if (updatedRows != 0) {
+ updateIssueImpacts(dto, mapper(session));
+ }
+ }
+
+ public List<IssueDto> selectByKeysIfNotUpdatedAt(DbSession session, List<String> keys, long updatedAt) {
+ return mapper(session).selectByKeysIfNotUpdatedAt(keys, updatedAt);
}
public void insertAsNewCodeOnReferenceBranch(DbSession session, NewCodeReferenceIssueDto dto) {
import javax.annotation.Nullable;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
+import org.jetbrains.annotations.NotNull;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.util.Uuids;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.protobuf.DbIssues;
import org.sonar.db.rule.RuleDto;
.setQuickFixAvailable(issue.isQuickFixAvailable())
.setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue())
.setCodeVariants(issue.codeVariants())
-
+ .replaceAllImpacts(mapToImpactDto(issue.impacts()))
// technical dates
.setCreatedAt(now)
.setUpdatedAt(now);
}
+ @NotNull
+ private static Set<ImpactDto> mapToImpactDto(Map<SoftwareQuality, Severity> impacts) {
+ return impacts.entrySet().stream().map(e -> new ImpactDto()
+ .setUuid(Uuids.createFast())
+ .setSoftwareQuality(e.getKey())
+ .setSeverity(e.getValue()))
+ .collect(Collectors.toSet());
+ }
+
/**
* On server side, we need component keys and uuid
*/
.setQuickFixAvailable(issue.isQuickFixAvailable())
.setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue())
.setCodeVariants(issue.codeVariants())
-
+ .replaceAllImpacts(mapToImpactDto(issue.impacts()))
// technical date
.setUpdatedAt(now);
}
issue.setQuickFixAvailable(quickFixAvailable);
issue.setIsNewCodeReferenceIssue(isNewCodeReferenceIssue);
issue.setCodeVariants(getCodeVariants());
+ impacts.forEach(i -> issue.addImpact(i.getSoftwareQuality(), i.getSeverity()));
return issue;
}
}
import java.util.stream.Stream;
import org.apache.commons.lang.math.RandomUtils;
import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.rule.Severity;
import org.sonar.core.util.UuidFactoryFast;
.setStatus(Issue.STATUS_OPEN)
.setResolution(null)
.setSeverity(Severity.ALL.get(nextInt(Severity.ALL.size())))
+ //TODO map to correct impact
+ .addImpact(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(org.sonar.api.issue.impact.Severity.HIGH))
.setEffort((long) RandomUtils.nextInt(10))
.setAssigneeUuid("assignee-uuid_" + randomAlphabetic(26))
.setAuthorLogin("author_" + randomAlphabetic(5))
.setIssueCreationDate(createdAt)
.setIssueUpdateDate(updatedAt)
.setIssueCloseDate(closedAt)
- .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
+ .setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
+ .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH));
DefaultIssue expected = new DefaultIssue()
.setKey("100")
.setIsNewCodeReferenceIssue(false)
.setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
.setCodeVariants(Set.of())
- .setTags(Set.of());
+ .setTags(Set.of())
+ .addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
DefaultIssue issue = dto.toDefaultIssue();
assertThat(issueDto.isQuickFixAvailable()).isTrue();
assertThat(issueDto.isNewCodeReferenceIssue()).isTrue();
assertThat(issueDto.getOptionalRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
+ assertThat(issueDto.getImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+ .containsExactlyInAnyOrder(tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH), tuple(SoftwareQuality.RELIABILITY, Severity.LOW));
}
@Test
assertThat(issueDto.isQuickFixAvailable()).isTrue();
assertThat(issueDto.isNewCodeReferenceIssue()).isTrue();
assertThat(issueDto.getOptionalRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
+ assertThat(issueDto.getImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+ .containsExactlyInAnyOrder(tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH), tuple(SoftwareQuality.RELIABILITY, Severity.LOW));
}
private DefaultIssue createExampleDefaultIssue(Date dateNow) {
.setQuickFixAvailable(true)
.setIsNewCodeReferenceIssue(true)
.setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
- .setCodeVariants(List.of("variant1", "variant2"));
+ .setCodeVariants(List.of("variant1", "variant2"))
+ .addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
+ .addImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
return defaultIssue;
}
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Date;
+import java.util.EnumMap;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.issue.impact.Severity;
+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.RuleTagFormat;
for (int i = 0; i < l1c.getMessageFormattingCount(); i++) {
if (l1c.getMessageFormatting(i).getStart() != l2.getMessageFormatting(i).getStart()
- || l1c.getMessageFormatting(i).getEnd() != l2.getMessageFormatting(i).getEnd()
- || l1c.getMessageFormatting(i).getType() != l2.getMessageFormatting(i).getType()) {
+ || l1c.getMessageFormatting(i).getEnd() != l2.getMessageFormatting(i).getEnd()
+ || l1c.getMessageFormatting(i).getType() != l2.getMessageFormatting(i).getType()) {
return false;
}
}
issue.setMessage(previousMessage);
issue.setMessageFormattings(previousMessageFormattings);
boolean changed = setMessage(issue, currentMessage, context);
- return setMessageFormattings(issue, currentMessageFormattings, context) || changed;
+ return setMessageFormattings(issue, currentMessageFormattings, context) || changed;
}
public void addComment(DefaultIssue issue, String text, IssueChangeContext context) {
return false;
}
+ public boolean setImpacts(DefaultIssue issue, Map<SoftwareQuality, Severity> previousImpacts, IssueChangeContext context) {
+ Map<SoftwareQuality, Severity> currentImpacts = new EnumMap<>(issue.impacts());
+ if (!previousImpacts.equals(currentImpacts)) {
+ issue.replaceImpacts(currentImpacts);
+ issue.setUpdateDate(context.date());
+ issue.setChanged(true);
+ return true;
+ }
+ return false;
+ }
+
private static Set<String> getNewCodeVariants(DefaultIssue issue) {
Set<String> issueCodeVariants = issue.codeVariants();
if (issueCodeVariants == null) {
import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.commons.lang.time.DateUtils;
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.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
assertThat(issue.mustSendNotifications()).isTrue();
}
+ @Test
+ public void setImpacts_whenImpactAdded_shouldBeUpdated() {
+ Map<SoftwareQuality, Severity> currentImpacts = Map.of(SoftwareQuality.RELIABILITY, Severity.LOW);
+ Map<SoftwareQuality, Severity> newImpacts = Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
+
+ issue.replaceImpacts(newImpacts);
+ boolean updated = underTest.setImpacts(issue, currentImpacts, context);
+ assertThat(updated).isTrue();
+ assertThat(issue.impacts()).isEqualTo(newImpacts);
+ }
+
@Test
public void setCodeVariants_whenCodeVariantsUnchanged_shouldNotBeUpdated() {
Set<String> currentCodeVariants = new HashSet<>(Arrays.asList("linux", "windows"));
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
private String anticipatedTransitionUuid = null;
+ private Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts = new EnumMap<>(SoftwareQuality.class);
+
@Override
public String key() {
return key;
@Override
public Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts() {
- return Collections.emptyMap();
+ return impacts;
+ }
+
+ public DefaultIssue replaceImpacts(Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts) {
+ this.impacts.clear();
+ this.impacts.putAll(impacts);
+ return this;
+ }
+
+ public DefaultIssue addImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) {
+ impacts.put(softwareQuality, severity);
+ return this;
}
public DefaultIssue setType(RuleType type) {
package org.sonar.core.issue;
import java.util.List;
+import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.utils.Duration;
import static org.assertj.core.api.Assertions.assertThat;
DefaultIssue defaultIssue = new DefaultIssue();
defaultIssue.setAnticipatedTransitionUuid("uuid");
assertThat(defaultIssue.getAnticipatedTransitionUuid()).isPresent();
+
+ }
+
+ public void getImpacts_whenAddingNewImpacts_shouldReturnListOfImpacts() {
+ issue.addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
+ issue.addImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
+
+ assertThat(issue.impacts()).containsExactlyEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH, SoftwareQuality.RELIABILITY, Severity.LOW));
+ }
+
+ @Test
+ public void replaceImpacts_shouldreplaceExistingImpacts() {
+ issue.addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
+ issue.addImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
+
+ issue.replaceImpacts(Map.of(SoftwareQuality.SECURITY, Severity.LOW));
+
+ assertThat(issue.impacts()).containsExactlyEntriesOf(Map.of(SoftwareQuality.SECURITY, Severity.LOW));
}
}