]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20021 persist impacts from report to Database
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Wed, 9 Aug 2023 12:18:17 +0000 (14:18 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 18 Aug 2023 20:02:48 +0000 (20:02 +0000)
27 files changed:
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/UpdateConflictResolver.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStep.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/util/cache/ProtobufIssueDiskCache.java
server/sonar-ce-task-projectanalysis/src/main/protobuf/issue_cache.proto
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/UpdateConflictResolverTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/util/cache/ProtobufIssueDiskCacheTest.java
server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java
server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueMapperIT.java
server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeCommandsIT.java
server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueTesting.java
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueFieldsSetter.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueFieldsSetterTest.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java

index 77e630aaa390baf17574653ff2eccf26045bdbd2..ae12303dc0aab16bcd641001eb93bcbc1c819977 100644 (file)
@@ -28,6 +28,8 @@ import org.junit.Rule;
 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;
@@ -51,8 +53,8 @@ import org.sonar.db.DbTester;
 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;
@@ -61,6 +63,7 @@ import org.sonar.server.issue.IssueStorage;
 
 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;
@@ -314,7 +317,7 @@ public class PersistIssuesStepIT extends BaseStepTest {
     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"));
@@ -343,6 +346,7 @@ public class PersistIssuesStepIT extends BaseStepTest {
       .setCreationDate(new Date(NOW))
       .setNew(true)
       .setIsOnChangedLine(true)
+      .addImpact(SoftwareQuality.SECURITY, Severity.MEDIUM)
       .setType(RuleType.BUG)).close();
 
     TestComputationStepContext context = new TestComputationStepContext();
@@ -356,6 +360,8 @@ public class PersistIssuesStepIT extends BaseStepTest {
     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();
index 517dffec2538cf81c8e56165cd8bc538f243338f..39bffffc47e364dce18b6f34080ca6a347e7946c 100644 (file)
@@ -203,6 +203,7 @@ public class IssueLifecycle {
     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) {
index 4b6d801d5ca30f84d856176749f13fc904ef240c..f59c3a04d56fb4b5eaf83704d0c5076300a33e60 100644 (file)
  */
 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;
@@ -64,4 +67,6 @@ public interface Rule {
   String getSeverity();
 
   Set<String> getSecurityStandards();
+
+  Map<SoftwareQuality, Severity> getDefaultImpacts();
 }
index aa46996a31530b47054afee399bb1f1350c3d026..265e5c5103373872cd1dcf1c26dcd6126b3d7979 100644 (file)
 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;
@@ -52,6 +56,7 @@ public class RuleImpl implements Rule {
   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();
@@ -68,6 +73,8 @@ public class RuleImpl implements Rule {
     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) {
@@ -137,6 +144,11 @@ public class RuleImpl implements Rule {
     return securityStandards;
   }
 
+  @Override
+  public Map<SoftwareQuality, Severity> getDefaultImpacts() {
+    return defaultImpacts;
+  }
+
   @Override
   public boolean equals(@Nullable Object o) {
     if (this == o) {
index 6f5dd4d2f54108c1434f6c620a2a3670632bbbd6..aa7ad4e89fc20b762be4fc72cc76cc282096bac8 100644 (file)
@@ -27,6 +27,8 @@ import java.util.Optional;
 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;
@@ -226,5 +228,11 @@ public class RuleRepositoryImpl implements RuleRepository {
     public Set<String> getSecurityStandards() {
       return Collections.emptySet();
     }
+
+    @Override
+    public Map<SoftwareQuality, Severity> getDefaultImpacts() {
+      //TODO external issues
+      return Collections.emptyMap();
+    }
   }
 }
index 7996cafb8c00cb303d652b4526f2d7a5649f4f53..17fcfd71b475f7de3f51470f2b6add12187e594b 100644 (file)
@@ -25,12 +25,14 @@ import java.util.HashMap;
 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;
@@ -192,6 +194,8 @@ public class TrackerRawInputFactory {
       issue.setQuickFixAvailable(reportIssue.getQuickFixAvailable());
       issue.setRuleDescriptionContextKey(reportIssue.hasRuleDescriptionContextKey() ? reportIssue.getRuleDescriptionContextKey() : null);
       issue.setCodeVariants(reportIssue.getCodeVariantsList());
+
+      issue.replaceImpacts(convertImpacts(issue.ruleKey(), reportIssue.getOverridenImpactsList()));
       return issue;
     }
 
@@ -328,6 +332,16 @@ public class TrackerRawInputFactory {
     }
   }
 
+  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()
index 2d3dc830a4688eebe2e43dfd34bc12cef8fc4fad..16220799ee23b90790524b100c754e9763f57940 100644 (file)
@@ -23,8 +23,9 @@ import com.google.common.annotations.VisibleForTesting;
 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
@@ -34,10 +35,10 @@ public class UpdateConflictResolver {
 
   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
index 363b88507d4bc9e22c007ae84bd0a05de088adaa..9a7fea9364e2d28e11ac3a897f607344815d8f39 100644 (file)
@@ -38,6 +38,7 @@ import org.sonar.db.DbClient;
 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;
@@ -83,8 +84,7 @@ public class PersistIssuesStep implements ComputationStep {
       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()) {
@@ -92,33 +92,34 @@ public class PersistIssuesStep implements ComputationStep {
         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);
@@ -126,16 +127,16 @@ public class PersistIssuesStep implements ComputationStep {
   }
 
   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);
@@ -143,7 +144,8 @@ public class PersistIssuesStep implements ComputationStep {
     });
   }
 
-  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;
     }
@@ -151,19 +153,19 @@ public class PersistIssuesStep implements ComputationStep {
     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++;
         });
     }
@@ -171,26 +173,28 @@ public class PersistIssuesStep implements ComputationStep {
     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++;
     });
   }
index 69653021a602c931ba3716aa39b266b9a19f236b..4e015401a3b5555524a7dfb0512bef1c4b688c2a 100644 (file)
@@ -32,6 +32,8 @@ import java.util.Collections;
 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;
@@ -140,10 +142,12 @@ public class ProtobufIssueDiskCache implements DiskCache<DefaultIssue> {
       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;
   }
 
@@ -194,6 +198,13 @@ public class ProtobufIssueDiskCache implements DiskCache<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));
     }
index c75e6f585092675f60ec10a9c6a1f4d6028f2edd..77eabd0a214c57d718f935941192285d16fbcbcd 100644 (file)
@@ -84,6 +84,7 @@ message Issue {
   optional string codeVariants = 46;
   optional string assigneeLogin = 47;
   optional string anticipatedTransitionUuid = 48;
+  repeated Impact impacts = 49;
 }
 
 message Comment {
@@ -107,3 +108,8 @@ message Diff {
   optional string oldValue = 1;
   optional string newValue = 2;
 }
+
+message Impact {
+  required string software_quality = 1;
+  required string severity = 2;
+}
index 532ad64422ae5b0527d04a8267714999d5b44b4f..1e551f5dbebf112e89d359cd94d9cb9d547c5169 100644 (file)
  */
 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;
@@ -42,7 +46,8 @@ public class DumbRule implements Rule {
   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;
@@ -111,6 +116,11 @@ public class DumbRule implements Rule {
     return securityStandards;
   }
 
+  @Override
+  public Map<SoftwareQuality, Severity> getDefaultImpacts() {
+    return defaultImpacts;
+  }
+
   @Override
   public boolean isExternal() {
     return isExternal;
@@ -171,4 +181,9 @@ public class DumbRule implements Rule {
     return this;
   }
 
+  public DumbRule addDefaultImpact(SoftwareQuality softwareQuality, Severity severity) {
+    defaultImpacts.put(softwareQuality, severity);
+    return this;
+  }
+
 }
index e995540494e23a34b4b1a0824360cbc3e8a82b2b..8c925bc46fb140bafa30fcd8819dfefe32ed7ac2 100644 (file)
@@ -23,6 +23,8 @@ import java.util.Date;
 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;
@@ -386,6 +388,7 @@ public class IssueLifecycleTest {
       .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"));
@@ -425,6 +428,7 @@ public class IssueLifecycleTest {
       .setGap(15d)
       .setRuleDescriptionContextKey("hibernate")
       .setCodeVariants(Set.of("donut"))
+      .addImpact(SoftwareQuality.RELIABILITY, Severity.LOW)
       .setEffort(Duration.create(15L))
       .setManualSeverity(false)
       .setLocations(issueLocations)
@@ -455,7 +459,8 @@ public class IssueLifecycleTest {
       .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");
index 67647062a1d53023430bdcab231f87c117813eb4..a40e033b5f68c4abc07636199fac7e3a849eda32 100644 (file)
@@ -25,11 +25,14 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 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;
@@ -123,11 +126,15 @@ public class TrackerRawInputFactoryTest {
   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)
@@ -162,6 +169,7 @@ public class TrackerRawInputFactoryTest {
     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
@@ -231,11 +239,35 @@ public class TrackerRawInputFactoryTest {
       .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())
@@ -350,7 +382,7 @@ public class TrackerRawInputFactoryTest {
 
   @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},
@@ -474,9 +506,10 @@ public class TrackerRawInputFactoryTest {
     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);
   }
 }
index 88919c1cb57e4a42af8207f767e64e3609e9822d..19ebc222006dd6c50d49d25ddf39cd20f62feb5a 100644 (file)
@@ -26,10 +26,12 @@ import org.sonar.api.rule.RuleKey;
 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;
@@ -49,7 +51,8 @@ public class UpdateConflictResolverTest {
       .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)
@@ -63,10 +66,10 @@ public class UpdateConflictResolverTest {
       // 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");
index d55cb439f5090c4ce4b13fd5dbb357a284e2e518..340bb259769246d2f53c204679cddcf942cbf540 100644 (file)
 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;
@@ -52,6 +55,18 @@ public class ProtobufIssueDiskCacheTest {
     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();
@@ -73,6 +88,24 @@ public class ProtobufIssueDiskCacheTest {
     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");
index 74a267e7b07fb820c6000e4c5b2159d8febbece7..d9f8ac85ee9ed0e0cd861a5c233e3f715dec3d4b 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.api.rule.RuleKey;
 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;
@@ -786,6 +787,33 @@ public class IssueDaoIT {
     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);
   }
index 0572e11d34aa3dfd520eaf9c57de923e66c9e06f..06a8d73a67ce6eb75d9fce883673451e48ffca94 100644 (file)
@@ -192,7 +192,7 @@ public class IssueMapperIT {
       .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();
@@ -227,7 +227,7 @@ public class IssueMapperIT {
       .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();
index 937dc4d348b0708d1aae74d2c1dcbb2312409ac0..b89a854e0090b7795a5a630f93d91920dfb0c6a1 100644 (file)
@@ -582,7 +582,7 @@ public class PurgeCommandsIT {
     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() + "'");
     });
index 9d0e6ccb808784a7cad84a520533a6183d76c2b7..5fa203103b0e1ce534c07f61fb20e395e77a4a19 100644 (file)
@@ -1216,7 +1216,7 @@ public class PurgeDaoIT {
     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);
index 905c0aa70c9224d819486dcc91c383cb20af0947..6efd896f3925382a777255b11347df2610f2768b 100644 (file)
@@ -98,10 +98,10 @@ public class IssueDao implements Dao {
 
   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));
   }
@@ -115,6 +115,18 @@ public class IssueDao implements Dao {
 
   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) {
index af25c60b5eee07ecfaa1ef6d4f91ab1813e2a0dd..8d16aba747d8ef3d74d7dc89fedb32d7a8c66aef 100644 (file)
@@ -38,6 +38,7 @@ import javax.annotation.CheckForNull;
 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;
@@ -45,6 +46,7 @@ import org.sonar.api.rules.CleanCodeAttribute;
 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;
@@ -154,12 +156,21 @@ public final class IssueDto implements Serializable {
       .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
    */
@@ -202,7 +213,7 @@ public final class IssueDto implements Serializable {
       .setQuickFixAvailable(issue.isQuickFixAvailable())
       .setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue())
       .setCodeVariants(issue.codeVariants())
-
+      .replaceAllImpacts(mapToImpactDto(issue.impacts()))
       // technical date
       .setUpdatedAt(now);
   }
@@ -865,6 +876,7 @@ public final class IssueDto implements Serializable {
     issue.setQuickFixAvailable(quickFixAvailable);
     issue.setIsNewCodeReferenceIssue(isNewCodeReferenceIssue);
     issue.setCodeVariants(getCodeVariants());
+    impacts.forEach(i -> issue.addImpact(i.getSoftwareQuality(), i.getSeverity()));
     return issue;
   }
 }
index 201fbff439d31cac57ae52c6c2595b538acc15b0..86ff8712eb58beb7fea602b11fbf1aea4a672687 100644 (file)
@@ -25,6 +25,7 @@ import java.util.function.Function;
 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;
@@ -68,6 +69,8 @@ public class IssueTesting {
       .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))
index 66f510cb5a2d72307131b4065755d16de767b8c5..5bb26690d80c5666b83b7e04c1922cf0b77f0a6e 100644 (file)
@@ -83,7 +83,8 @@ public class IssueDtoTest {
       .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")
@@ -112,7 +113,8 @@ public class IssueDtoTest {
       .setIsNewCodeReferenceIssue(false)
       .setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
       .setCodeVariants(Set.of())
-      .setTags(Set.of());
+      .setTags(Set.of())
+      .addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
 
     DefaultIssue issue = dto.toDefaultIssue();
 
@@ -297,6 +299,8 @@ public class IssueDtoTest {
     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
@@ -327,6 +331,8 @@ public class IssueDtoTest {
     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) {
@@ -358,7 +364,9 @@ public class IssueDtoTest {
       .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;
   }
 
index b6d8b7443d973f8f658981fa7e0dc2e017df7ee9..0aeaa11cadd863f2d449507bd4271a8643bab2e1 100644 (file)
@@ -23,12 +23,17 @@ import com.google.common.base.Joiner;
 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;
@@ -319,8 +324,8 @@ public class IssueFieldsSetter {
 
     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;
       }
     }
@@ -333,7 +338,7 @@ public class IssueFieldsSetter {
     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) {
@@ -426,6 +431,17 @@ public class IssueFieldsSetter {
     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) {
index 362c7844d6757ba4cc5d526a2a472a58bf744e25..3003da52cea304b1e389996d740bc60054569eae 100644 (file)
@@ -24,10 +24,13 @@ import java.util.Calendar;
 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;
@@ -529,6 +532,17 @@ public class IssueFieldsSetterTest {
     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"));
index b26765e4c71b37b8e9382bb254886a632fee745c..581def46912649a85d142e36b9db94d38299df6c 100644 (file)
@@ -28,6 +28,7 @@ import java.util.ArrayList;
 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;
@@ -133,6 +134,8 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
 
   private String anticipatedTransitionUuid = null;
 
+  private Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts = new EnumMap<>(SoftwareQuality.class);
+
   @Override
   public String key() {
     return key;
@@ -150,7 +153,18 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
 
   @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) {
index 9d1309c78503e7df2fb8fd3a9a8e0bbfb2a3f6ca..a09c91c03dc197399f2f210fe8b162c2a95524f6 100644 (file)
 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;
@@ -288,5 +291,23 @@ public class DefaultIssueTest {
     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));
   }
 }