]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20021 Add impacts support in IssueDto, RuleDto
authorJacek Poreda <jacek.poreda@sonarsource.com>
Wed, 2 Aug 2023 11:41:31 +0000 (13:41 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 18 Aug 2023 20:02:48 +0000 (20:02 +0000)
23 files changed:
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.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/it/java/org/sonar/db/rule/RuleDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java [new file with mode: 0644]
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/IssueMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDtoTest.java
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java

index 896dd650288ad91f40ec8515686019f856cc53d9..092d7bf8791e267f7a1a993d7b02ee4248036bb7 100644 (file)
@@ -58,6 +58,7 @@ public final class SqTables {
     "internal_component_props",
     "internal_properties",
     "issues",
+    "issues_impacts",
     "issue_changes",
     "live_measures",
     "metrics",
@@ -96,6 +97,7 @@ public final class SqTables {
     "report_subscriptions",
     "rules",
     "rule_desc_sections",
+    "rules_default_impacts",
     "rules_parameters",
     "rules_profiles",
     "rule_repositories",
index 6d6d1f8d4c8d5d333932bbf513098d261d98b180..74a267e7b07fb820c6000e4c5b2159d8febbece7 100644 (file)
@@ -31,9 +31,12 @@ import javax.annotation.Nullable;
 import org.junit.Before;
 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.rule.RuleKey;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactoryFast;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.Pagination;
@@ -101,6 +104,8 @@ public class IssueDaoIT {
 
   @Before
   public void setup() {
+    int i = db.countSql(db.getSession(), "select count(1) from rules_default_impacts");
+
     db.rules().insert(RULE.setIsExternal(true));
     projectDto = db.components().insertPrivateProject(t -> t.setUuid(PROJECT_UUID).setKey(PROJECT_KEY)).getMainBranchComponent();
     db.components().insertComponent(newFileDto(projectDto).setUuid(FILE_UUID).setKey(FILE_KEY));
@@ -142,7 +147,7 @@ public class IssueDaoIT {
     IssueDto issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
 
     assertThat(issue).usingRecursiveComparison()
-      .ignoringFields("filePath", "issueCreationDate", "issueUpdateDate", "issueCloseDate", "cleanCodeAttribute")
+      .ignoringFields("filePath", "issueCreationDate", "issueUpdateDate", "issueCloseDate", "cleanCodeAttribute", "impacts", "ruleDefaultImpacts")
       .isEqualTo(expected);
     assertThat(issue.parseMessageFormattings()).isEqualTo(MESSAGE_FORMATTING);
     assertThat(issue.getIssueCreationDate()).isNotNull();
@@ -152,6 +157,18 @@ public class IssueDaoIT {
     assertThat(issue.getRule()).isEqualTo(RULE.getRuleKey());
     assertThat(issue.getCleanCodeAttribute()).isEqualTo(RULE.getCleanCodeAttribute());
     assertThat(issue.parseLocations()).isNull();
+    assertThat(issue.getImpacts())
+      .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
+      .containsExactlyInAnyOrder(
+        tuple(Severity.MEDIUM, SoftwareQuality.RELIABILITY),
+        tuple(Severity.LOW, SoftwareQuality.SECURITY));
+
+    assertThat(issue.getEffectiveImpacts())
+      // impacts from rule
+      .containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
+      // impacts from issue
+      .containsEntry(SoftwareQuality.RELIABILITY, Severity.MEDIUM)
+      .containsEntry(SoftwareQuality.SECURITY, Severity.LOW);
   }
 
   @Test
@@ -169,8 +186,15 @@ public class IssueDaoIT {
     prepareTables();
 
     List<IssueDto> issues = underTest.selectByKeys(db.getSession(), asList("I1", "I2", "I3"));
-    // results are not ordered, so do not use "containsExactly"
-    assertThat(issues).extracting("key").containsOnly("I1", "I2");
+
+    assertThat(issues).extracting(IssueDto::getKey).containsExactlyInAnyOrder("I1", "I2");
+    assertThat(issues).filteredOn(issueDto -> issueDto.getKey().equals("I1"))
+      .extracting(IssueDto::getImpacts)
+      .flatMap(issueImpactDtos -> issueImpactDtos)
+      .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
+      .containsExactlyInAnyOrder(
+        tuple(Severity.MEDIUM, SoftwareQuality.RELIABILITY),
+        tuple(Severity.LOW, SoftwareQuality.SECURITY));
   }
 
   @Test
@@ -537,6 +561,32 @@ public class IssueDaoIT {
     assertThat(issue1.getOptionalRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
   }
 
+  @Test
+  public void insert_shouldInsertBatchIssuesWithImpacts() {
+    ImpactDto impact1 = new ImpactDto()
+      .setUuid(UuidFactoryFast.getInstance().create())
+      .setSoftwareQuality(SoftwareQuality.MAINTAINABILITY)
+      .setSeverity(Severity.HIGH);
+    ImpactDto impact2 = new ImpactDto()
+      .setUuid(UuidFactoryFast.getInstance().create())
+      .setSoftwareQuality(SoftwareQuality.SECURITY)
+      .setSeverity(Severity.LOW);
+    IssueDto issue1 = createIssueWithKey(ISSUE_KEY1)
+      .addImpact(impact1)
+      .addImpact(impact2);
+    IssueDto issue2 = createIssueWithKey(ISSUE_KEY2);
+    underTest.insert(db.getSession(), issue1, issue2);
+
+    List<IssueDto> issueDtos = underTest.selectByKeys(db.getSession(), Set.of(ISSUE_KEY1, ISSUE_KEY2));
+    assertThat(issueDtos)
+      .extracting(IssueDto::getKey)
+      .containsExactlyInAnyOrder(ISSUE_KEY1, ISSUE_KEY2);
+    assertThat(issueDtos).filteredOn(issueDto -> issueDto.getKey().equals(ISSUE_KEY1))
+      .flatExtracting(IssueDto::getImpacts)
+      .containsExactlyInAnyOrder(impact1, impact2)
+      .doesNotContainNull();
+  }
+
   @Test
   public void update_whenUpdatingRuleDescriptionContextKeyToNull_returnsEmptyContextKey() {
     IssueDto issue = createIssueWithKey(ISSUE_KEY1).setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
@@ -783,7 +833,9 @@ public class IssueDaoIT {
       .setMessage("the message")
       .setRuleUuid(RULE.getUuid())
       .setComponentUuid(FILE_UUID)
-      .setProjectUuid(PROJECT_UUID));
+      .setProjectUuid(PROJECT_UUID)
+      .addImpact(newIssueImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM))
+      .addImpact(newIssueImpact(SoftwareQuality.SECURITY, Severity.LOW)));
     underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2)
       .setRuleUuid(RULE.getUuid())
       .setComponentUuid(FILE_UUID)
@@ -792,6 +844,13 @@ public class IssueDaoIT {
     db.getSession().commit();
   }
 
+  private static ImpactDto newIssueImpact(SoftwareQuality softwareQuality, Severity severity) {
+    return new ImpactDto()
+      .setUuid(UuidFactoryFast.getInstance().create())
+      .setSoftwareQuality(softwareQuality)
+      .setSeverity(severity);
+  }
+
   private static RuleType randomRuleTypeExceptHotspot() {
     return RULE_TYPES_EXCEPT_HOTSPOT[nextInt(RULE_TYPES_EXCEPT_HOTSPOT.length)];
   }
index 308c4c81c7d4a0facf7ba6e67673f57eff490b34..0572e11d34aa3dfd520eaf9c57de923e66c9e06f 100644 (file)
@@ -38,6 +38,8 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
 import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.System2;
@@ -424,13 +426,10 @@ public class IssueMapperIT {
     underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler);
 
     assertThat(resultHandler.issues)
-      .hasSize(4)
+      .hasSize(1)
       .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
-      .containsExactly(
-        tuple(issue.getKey(), changes[2].getChangeData()),
-        tuple(issue.getKey(), changes[3].getChangeData()),
-        tuple(issue.getKey(), changes[0].getChangeData()),
-        tuple(issue.getKey(), changes[1].getChangeData()));
+      // we are interested only in the latest closed issue change data
+      .containsExactly(tuple(issue.getKey(), changes[2].getChangeData()));
   }
 
   @Test
@@ -450,12 +449,11 @@ public class IssueMapperIT {
     underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler);
 
     assertThat(resultHandler.issues)
-      .hasSize(3)
+      .hasSize(1)
       .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
       .containsExactly(
-        tuple(issue.getKey(), changes[2].getChangeData()),
-        tuple(issue.getKey(), changes[3].getChangeData()),
-        tuple(issue.getKey(), changes[1].getChangeData()));
+        // we are interested only in the latest closed issue change data
+        tuple(issue.getKey(), changes[2].getChangeData()));
   }
 
   private IssueChangeDto insertToClosedDiff(IssueDto issueDto) {
@@ -484,7 +482,7 @@ public class IssueMapperIT {
   }
 
   @SafeVarargs
-  private final IssueDto insertNewClosedIssue(ComponentDto component, RuleType ruleType, Consumer<IssueDto>... consumers) {
+  private IssueDto insertNewClosedIssue(ComponentDto component, RuleType ruleType, Consumer<IssueDto>... consumers) {
     RuleDto rule = dbTester.rules().insert(t -> t.setType(ruleType));
     return insertNewClosedIssue(component, rule, system2.now(), consumers);
   }
@@ -495,7 +493,7 @@ public class IssueMapperIT {
   }
 
   @SafeVarargs
-  private final IssueDto insertNewClosedIssue(ComponentDto component, RuleDto rule, long issueCloseTime, Consumer<IssueDto>... consumers) {
+  private IssueDto insertNewClosedIssue(ComponentDto component, RuleDto rule, long issueCloseTime, Consumer<IssueDto>... consumers) {
     IssueDto res = new IssueDto()
       .setKee(UuidFactoryFast.getInstance().create())
       .setRuleUuid(rule.getUuid())
@@ -503,9 +501,17 @@ public class IssueMapperIT {
       .setComponentUuid(component.uuid())
       .setProjectUuid(component.branchUuid())
       .setStatus(Issue.STATUS_CLOSED)
-      .setIssueCloseTime(issueCloseTime);
+      .setIssueCloseTime(issueCloseTime)
+      .addImpact(new ImpactDto()
+        .setUuid(UuidFactoryFast.getInstance().create())
+        .setSeverity(Severity.HIGH)
+        .setSoftwareQuality(SoftwareQuality.MAINTAINABILITY))
+      .addImpact(new ImpactDto()
+        .setUuid(UuidFactoryFast.getInstance().create())
+        .setSeverity(Severity.LOW)
+        .setSoftwareQuality(SoftwareQuality.SECURITY));
     Arrays.asList(consumers).forEach(c -> c.accept(res));
-    underTest.insert(res);
+    dbTester.getDbClient().issueDao().insert(dbSession, res);
     dbSession.commit();
     return res;
   }
index 476ee26358934e935d41ec3e1b8cbc0def85f489..937dc4d348b0708d1aae74d2c1dcbb2312409ac0 100644 (file)
@@ -37,6 +37,8 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
 import org.sonar.api.utils.System2;
 import org.sonar.core.util.UuidFactoryFast;
 import org.sonar.db.DbTester;
@@ -50,6 +52,7 @@ import org.sonar.db.dialect.Dialect;
 import org.sonar.db.duplication.DuplicationUnitDto;
 import org.sonar.db.entity.EntityDto;
 import org.sonar.db.issue.AnticipatedTransitionDto;
+import org.sonar.db.issue.ImpactDto;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.db.newcodeperiod.NewCodePeriodType;
@@ -565,7 +568,7 @@ public class PurgeCommandsIT {
     underTest.deleteIssues(projectOrView.uuid());
 
     assertThat(dbTester.countSql("select count(uuid) from new_code_reference_issues where issue_key in (" +
-      String.join(", ", issueKeys) + ")")).isZero();
+                                 String.join(", ", issueKeys) + ")")).isZero();
   }
 
   @Test
@@ -957,14 +960,14 @@ public class PurgeCommandsIT {
 
   @DataProvider
   public static Object[] projects() {
-    return new Object[]{
+    return new Object[] {
       ComponentTesting.newPrivateProjectDto(), ComponentTesting.newPublicProjectDto(),
     };
   }
 
   @DataProvider
   public static Object[] views() {
-    return new Object[]{
+    return new Object[] {
       ComponentTesting.newPortfolio(), ComponentTesting.newApplication()
     };
   }
index 1cf200413ee31b5bc04a800fe0467cc43d1b8a31..9d0e6ccb808784a7cad84a520533a6183d76c2b7 100644 (file)
@@ -1226,6 +1226,7 @@ public class PurgeDaoIT {
 
     assertThat(db.countRowsOfTable("issue_changes")).isZero();
     assertThat(db.countRowsOfTable("new_code_reference_issues")).isZero();
+    assertThat(db.countRowsOfTable("issues_impacts")).isZero();
     assertThat(db.select("select kee as \"KEE\" from issues")).extracting(i -> i.get("KEE")).containsOnly(issue1.getKey());
   }
 
index d705cfdf883bb709ad0a7f61ebf0133d01bb7a4c..d8a5fb2d4aacfaed6ebb5736908e2ad64557e08f 100644 (file)
@@ -29,11 +29,13 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import org.apache.commons.lang.RandomStringUtils;
 import org.apache.ibatis.exceptions.PersistenceException;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Rule;
 import org.junit.Test;
+import org.sonar.api.issue.impact.SoftwareQuality;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rule.Severity;
@@ -44,8 +46,10 @@ import org.sonar.api.server.debt.DebtRemediationFunction;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.System2;
 import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.RowNotFoundException;
+import org.sonar.db.issue.ImpactDto;
 import org.sonar.db.rule.RuleDto.Scope;
 
 import static com.google.common.collect.Sets.newHashSet;
@@ -56,6 +60,9 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
+import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
+import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY;
 import static org.sonar.api.rule.RuleStatus.REMOVED;
 
 public class RuleDaoIT {
@@ -68,12 +75,18 @@ public class RuleDaoIT {
 
   @Test
   public void selectByKey() {
-    RuleDto ruleDto = db.rules().insert();
+    RuleDto ruleDto = db.rules().insert(r -> r.addDefaultImpact(newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.LOW)));
 
     assertThat(underTest.selectByKey(db.getSession(), RuleKey.of("foo", "bar")))
       .isEmpty();
     RuleDto actualRule = underTest.selectByKey(db.getSession(), ruleDto.getKey()).get();
     assertEquals(actualRule, ruleDto);
+
+    assertThat(actualRule.getDefaultImpacts())
+      .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+      .containsExactlyInAnyOrder(
+        tuple(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH),
+        tuple(SECURITY, org.sonar.api.issue.impact.Severity.LOW));
   }
 
   @Test
@@ -83,7 +96,6 @@ public class RuleDaoIT {
     assertThat(underTest.selectByKey(db.getSession(), ruleDto.getKey())).isNotEmpty();
   }
 
-
   @Test
   public void selectByUuid() {
     RuleDto ruleDto = db.rules().insert();
@@ -153,14 +165,9 @@ public class RuleDaoIT {
 
   @Test
   public void selectOrFailByKey_fails_if_rule_not_found() {
-    assertThatThrownBy(() -> underTest.selectOrFailByKey(db.getSession(), RuleKey.of("NOT", "FOUND")))
-      .isInstanceOf(RowNotFoundException.class)
-      .hasMessage("Rule with key 'NOT:FOUND' does not exist");
-  }
-
-  @Test
-  public void selectOrFailDefinitionByKey_fails_if_rule_not_found() {
-    assertThatThrownBy(() -> underTest.selectOrFailByKey(db.getSession(), RuleKey.of("NOT", "FOUND")))
+    DbSession session = db.getSession();
+    RuleKey ruleKey = RuleKey.of("NOT", "FOUND");
+    assertThatThrownBy(() -> underTest.selectOrFailByKey(session, ruleKey))
       .isInstanceOf(RowNotFoundException.class)
       .hasMessage("Rule with key 'NOT:FOUND' does not exist");
   }
@@ -192,13 +199,40 @@ public class RuleDaoIT {
 
   @Test
   public void selectAll() {
-    RuleDto rule1 = db.rules().insertRule();
-    RuleDto rule2 = db.rules().insertRule();
+    RuleDto rule1 = db.rules().insertRule(r -> r.addDefaultImpact(newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.LOW)));
+    RuleDto rule2 = db.rules().insertRule(r -> r.addDefaultImpact(newRuleDefaultImpact(RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)));
     RuleDto rule3 = db.rules().insertRule();
 
-    assertThat(underTest.selectAll(db.getSession()))
+    List<RuleDto> ruleDtos = underTest.selectAll(db.getSession());
+    assertThat(ruleDtos)
       .extracting(RuleDto::getUuid)
-      .containsOnly(rule1.getUuid(), rule2.getUuid(), rule3.getUuid());
+      .containsExactlyInAnyOrder(rule1.getUuid(), rule2.getUuid(), rule3.getUuid());
+
+    assertThat(ruleDtos)
+      .filteredOn(ruleDto -> ruleDto.getUuid().equals(rule1.getUuid()))
+      .extracting(RuleDto::getDefaultImpacts)
+      .flatMap(Function.identity())
+      .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
+      .containsExactlyInAnyOrder(
+        tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY),
+        tuple(org.sonar.api.issue.impact.Severity.LOW, SECURITY));
+
+    assertThat(ruleDtos)
+      .filteredOn(ruleDto -> ruleDto.getUuid().equals(rule2.getUuid()))
+      .extracting(RuleDto::getDefaultImpacts)
+      .flatMap(Function.identity())
+      .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
+      .containsExactlyInAnyOrder(
+        tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY),
+        tuple(org.sonar.api.issue.impact.Severity.MEDIUM, RELIABILITY));
+
+    assertThat(ruleDtos)
+      .filteredOn(ruleDto -> ruleDto.getUuid().equals(rule3.getUuid()))
+      .extracting(RuleDto::getDefaultImpacts)
+      .flatMap(Function.identity())
+      .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
+      .containsExactlyInAnyOrder(
+        tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY));
   }
 
   private void assertEquals(RuleDto actual, RuleDto expected) {
@@ -313,8 +347,7 @@ public class RuleDaoIT {
       .extracting(RuleDto::getUuid, RuleDto::getLanguage, RuleDto::getType)
       .containsExactlyInAnyOrder(
         tuple(rule1.getUuid(), "java", RuleType.VULNERABILITY.getDbConstant()),
-        tuple(rule3.getUuid(), "java", RuleType.BUG.getDbConstant())
-      );
+        tuple(rule3.getUuid(), "java", RuleType.BUG.getDbConstant()));
 
     assertThat(underTest.selectByLanguage(db.getSession(), "js")).hasSize(1);
 
@@ -436,6 +469,7 @@ public class RuleDaoIT {
   @Test
   public void insert() {
     RuleDescriptionSectionDto sectionDto = createDefaultRuleDescriptionSection();
+    ImpactDto ruleDefaultImpactDto = newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.HIGH);
     RuleDto newRule = new RuleDto()
       .setUuid("rule-uuid")
       .setRuleKey("NewRuleKey")
@@ -443,6 +477,7 @@ public class RuleDaoIT {
       .setName("new name")
       .setDescriptionFormat(RuleDto.Format.MARKDOWN)
       .addRuleDescriptionSectionDto(sectionDto)
+      .addDefaultImpact(ruleDefaultImpactDto)
       .setStatus(RuleStatus.DEPRECATED)
       .setConfigKey("NewConfigKey")
       .setSeverity(Severity.INFO)
@@ -492,6 +527,8 @@ public class RuleDaoIT {
     assertThat(ruleDto.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator()
       .containsOnly(sectionDto);
     assertThat(ruleDto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
+    assertThat(ruleDto.getDefaultImpacts()).usingRecursiveFieldByFieldElementComparator()
+      .containsOnly(ruleDefaultImpactDto);
   }
 
   @Test
@@ -1148,6 +1185,13 @@ public class RuleDaoIT {
       .isInstanceOf(PersistenceException.class);
   }
 
+  private static ImpactDto newRuleDefaultImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) {
+    return new ImpactDto()
+      .setUuid(UuidFactoryFast.getInstance().create())
+      .setSoftwareQuality(softwareQuality)
+      .setSeverity(severity);
+  }
+
   private static RuleDescriptionSectionDto createDefaultRuleDescriptionSection() {
     return RuleDescriptionSectionDto.createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), RandomStringUtils.randomAlphanumeric(1000));
   }
index 4d670509d6763ff08ba8790bebe029cfba9199b1..acfd0374e1ffb6389f850480d7167a1e6725104b 100644 (file)
@@ -72,6 +72,7 @@ import org.sonar.db.event.EventDto;
 import org.sonar.db.event.EventMapper;
 import org.sonar.db.issue.AnticipatedTransitionDto;
 import org.sonar.db.issue.AnticipatedTransitionMapper;
+import org.sonar.db.issue.ImpactDto;
 import org.sonar.db.issue.IssueChangeDto;
 import org.sonar.db.issue.IssueChangeMapper;
 import org.sonar.db.issue.IssueDto;
@@ -212,6 +213,7 @@ public class MyBatis {
     confBuilder.loadAlias("InternalComponentProperty", InternalComponentPropertyDto.class);
     confBuilder.loadAlias("IssueChange", IssueChangeDto.class);
     confBuilder.loadAlias("KeyLongValue", KeyLongValue.class);
+    confBuilder.loadAlias("Impact", ImpactDto.class);
     confBuilder.loadAlias("Issue", IssueDto.class);
     confBuilder.loadAlias("NewCodeReferenceIssue", NewCodeReferenceIssueDto.class);
     confBuilder.loadAlias("Measure", MeasureDto.class);
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java
new file mode 100644 (file)
index 0000000..b62469b
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.io.Serializable;
+import java.util.Objects;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+public class ImpactDto implements Serializable {
+  private String uuid;
+  private SoftwareQuality softwareQuality;
+  private Severity severity;
+
+  public ImpactDto() {
+    // nothing to do
+  }
+
+  public String getUuid() {
+    return uuid;
+  }
+
+  public ImpactDto setUuid(String uuid) {
+    this.uuid = uuid;
+    return this;
+  }
+
+  public SoftwareQuality getSoftwareQuality() {
+    return softwareQuality;
+  }
+
+  public ImpactDto setSoftwareQuality(SoftwareQuality softwareQuality) {
+    this.softwareQuality = softwareQuality;
+    return this;
+  }
+
+  public Severity getSeverity() {
+    return severity;
+  }
+
+  public ImpactDto setSeverity(Severity severity) {
+    this.severity = severity;
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    ImpactDto impactDto = (ImpactDto) o;
+    return Objects.equals(uuid, impactDto.uuid)
+      && Objects.equals(softwareQuality, impactDto.softwareQuality)
+      && Objects.equals(severity, impactDto.severity);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(uuid, softwareQuality, severity);
+  }
+}
index 4ff0349eef90fbc02cd6436b9cab98fc034ca28d..905c0aa70c9224d819486dcc91c383cb20af0947 100644 (file)
@@ -93,13 +93,23 @@ public class IssueDao implements Dao {
 
   public void insert(DbSession session, IssueDto dto) {
     mapper(session).insert(dto);
+    updateIssueImpacts(dto, mapper(session));
+  }
+
+  private static void updateIssueImpacts(IssueDto issueDto, IssueMapper mapper) {
+    mapper.deleteIssueImpacts(issueDto.getKey());
+    insertInsertIssueImpacts(issueDto, mapper);
+  }
+
+  private static void insertInsertIssueImpacts(IssueDto issueDto, IssueMapper mapper) {
+    issueDto.getImpacts()
+      .forEach(impact -> mapper.insertIssueImpact(issueDto.getKey(), impact));
   }
 
   public void insert(DbSession session, IssueDto dto, IssueDto... others) {
-    IssueMapper mapper = mapper(session);
-    mapper.insert(dto);
+    insert(session, dto);
     for (IssueDto other : others) {
-      mapper.insert(other);
+      insert(session, other);
     }
   }
 
index 0cd237eaedb32242480663476fb9d1c5b49d09bf..af25c60b5eee07ecfaa1ef6d4f91ab1813e2a0dd 100644 (file)
@@ -26,13 +26,20 @@ import com.google.common.collect.ImmutableSet;
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.io.Serializable;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
+import java.util.EnumMap;
+import java.util.HashSet;
+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 org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
+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.CleanCodeAttribute;
 import org.sonar.api.rules.RuleType;
@@ -43,6 +50,7 @@ import org.sonar.db.protobuf.DbIssues;
 import org.sonar.db.rule.RuleDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
 import static org.sonar.api.utils.DateUtils.dateToLong;
 import static org.sonar.api.utils.DateUtils.longToDate;
 
@@ -103,6 +111,9 @@ public final class IssueDto implements Serializable {
   // populate only when retrieving closed issue for issue tracking
   private String closedChangeData;
 
+  private Set<ImpactDto> impacts = new HashSet<>();
+  private Set<ImpactDto> ruleDefaultImpacts = new HashSet<>();
+
   public IssueDto() {
     // nothing to do
   }
@@ -293,7 +304,7 @@ public final class IssueDto implements Serializable {
       try {
         return DbIssues.MessageFormattings.parseFrom(messageFormattings);
       } catch (InvalidProtocolBufferException e) {
-        throw new IllegalStateException(String.format("Fail to read ISSUES.MESSAGE_FORMATTINGS [KEE=%s]", kee), e);
+        throw new IllegalStateException(format("Fail to read ISSUES.MESSAGE_FORMATTINGS [KEE=%s]", kee), e);
       }
     }
     return null;
@@ -695,7 +706,7 @@ public final class IssueDto implements Serializable {
       try {
         return DbIssues.Locations.parseFrom(locations);
       } catch (InvalidProtocolBufferException e) {
-        throw new IllegalStateException(String.format("Fail to read ISSUES.LOCATIONS [KEE=%s]", kee), e);
+        throw new IllegalStateException(format("Fail to read ISSUES.LOCATIONS [KEE=%s]", kee), e);
       }
     }
     return null;
@@ -769,6 +780,51 @@ public final class IssueDto implements Serializable {
     return this;
   }
 
+  /**
+   * Return impacts defined on this issue.
+   *
+   * @return Collection of impacts
+   */
+  public Set<ImpactDto> getImpacts() {
+    return impacts;
+  }
+
+  public IssueDto addImpact(ImpactDto impact) {
+    impacts.stream().filter(impactDto -> impactDto.getSoftwareQuality() == impact.getSoftwareQuality()).findFirst()
+      .ifPresent(impactDto -> {
+        throw new IllegalStateException(format("Impact already defined on issue for Software Quality [%s]", impact.getSoftwareQuality()));
+      });
+
+    impacts.add(impact);
+    return this;
+  }
+
+  public IssueDto replaceAllImpacts(Collection<ImpactDto> newImpacts) {
+    Set<SoftwareQuality> newSoftwareQuality = newImpacts.stream().map(ImpactDto::getSoftwareQuality).collect(Collectors.toSet());
+    if (newSoftwareQuality.size() != newImpacts.size()) {
+      throw new IllegalStateException("Impacts must have unique Software Quality values");
+    }
+    impacts.clear();
+    impacts.addAll(newImpacts);
+    return this;
+  }
+
+  Set<ImpactDto> getRuleDefaultImpacts() {
+    return ruleDefaultImpacts;
+  }
+
+  /**
+   * Returns effective impacts defined on this issue along with default ones.
+   *
+   * @return Unmodifiable Map of impacts
+   */
+  public Map<SoftwareQuality, Severity> getEffectiveImpacts() {
+    EnumMap<SoftwareQuality, Severity> effectiveImpacts = new EnumMap<>(SoftwareQuality.class);
+    ruleDefaultImpacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity()));
+    impacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity()));
+    return Collections.unmodifiableMap(effectiveImpacts);
+  }
+
   @Override
   public String toString() {
     return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
index 5191504426a1e2762fa7cc71237c7345f77c2ec5..579c69c13f6c69b8bfdae36971cdb5a832465752 100644 (file)
@@ -62,6 +62,8 @@ public interface IssueMapper {
 
   void insertAsNewCodeOnReferenceBranch(NewCodeReferenceIssueDto issue);
 
+  void insertIssueImpact(@Param("issueKey") String issueKey, @Param("dto") ImpactDto issue);
+
   void deleteAsNewCodeOnReferenceBranch(String issueKey);
 
   int updateIfBeforeSelectedDate(IssueDto issue);
@@ -74,8 +76,9 @@ public interface IssueMapper {
 
   List<IssueDto> selectByBranch(@Param("keys") Set<String> keys, @Nullable @Param("changedSince") Long changedSince);
 
-
   List<String> selectRecentlyClosedIssues(@Param("queryParams") IssueQueryParams issueQueryParams);
 
   List<String> selectIssueKeysByQuery(@Param("query") IssueListQuery issueListQuery, @Param("pagination") Pagination pagination);
+
+  void deleteIssueImpacts(String issueKey);
 }
index 610bb1868c0bfd5761c9bd0188929bbbde06f7f8..0c086d15e0863edf22dc73b243e0fad11c9e8e62 100644 (file)
@@ -198,6 +198,11 @@ class PurgeCommands {
     session.commit();
     profiler.stop();
 
+    profiler.start("deleteIssues (issues_impacts)");
+    purgeMapper.deleteIssuesImpactsByProjectUuid(rootUuid);
+    session.commit();
+    profiler.stop();
+
     profiler.start("deleteIssues (issues)");
     purgeMapper.deleteIssuesByProjectUuid(rootUuid);
     session.commit();
index 9ceba885c7a00265335526cccb12a7f685102206..a1bcec6137e5fd6ca128ad1e2bc2cdc798756f9d 100644 (file)
@@ -139,6 +139,11 @@ public class PurgeDao implements Dao {
       return emptyList();
     });
 
+    executeLargeInputs(issueKeys, input -> {
+      mapper.deleteIssuesImpactsFromKeys(input);
+      return emptyList();
+    });
+
     executeLargeInputs(issueKeys, input -> {
       mapper.deleteIssuesFromKeys(input);
       return emptyList();
index 44651d1e8726707bfa067c24f851e4bd73609450..9f702aec832cd67457e867eb5ac3ba58c595c0b2 100644 (file)
@@ -92,6 +92,8 @@ public interface PurgeMapper {
 
   void deleteNewCodeReferenceIssuesByProjectUuid(@Param("projectUuid") String projectUuid);
 
+  void deleteIssuesImpactsByProjectUuid(@Param("projectUuid") String projectUuid);
+
   List<String> selectOldClosedIssueKeys(@Param("projectUuid") String projectUuid, @Nullable @Param("toDate") Long toDate);
 
   List<String> selectStaleBranchesAndPullRequests(@Param("projectUuid") String projectUuid, @Param("toDate") Long toDate);
@@ -107,6 +109,8 @@ public interface PurgeMapper {
 
   void deleteNewCodeReferenceIssuesFromKeys(@Param("issueKeys") List<String> keys);
 
+  void deleteIssuesImpactsFromKeys(@Param("issueKeys") List<String> keys);
+
   void deleteFileSourcesByProjectUuid(String rootProjectUuid);
 
   void deleteFileSourcesByFileUuid(@Param("fileUuids") List<String> fileUuids);
index 8a332eeba65d9b06296f9f2f2922acd9672f29fc..1af002cc859bc72f55afadb23a4908c50ea42e94 100644 (file)
@@ -102,12 +102,14 @@ public class RuleDao implements Dao {
     RuleMapper mapper = mapper(session);
     mapper.insertRule(ruleDto);
     updateRuleDescriptionSectionDtos(ruleDto, mapper);
+    updateRuleDefaultImpacts(ruleDto, mapper);
   }
 
   public void update(DbSession session, RuleDto ruleDto) {
     RuleMapper mapper = mapper(session);
     mapper.updateRule(ruleDto);
     updateRuleDescriptionSectionDtos(ruleDto, mapper);
+    updateRuleDefaultImpacts(ruleDto, mapper);
   }
 
   private static void updateRuleDescriptionSectionDtos(RuleDto ruleDto, RuleMapper mapper) {
@@ -120,6 +122,16 @@ public class RuleDao implements Dao {
       .forEach(section -> mapper.insertRuleDescriptionSection(ruleDto.getUuid(), section));
   }
 
+  private static void updateRuleDefaultImpacts(RuleDto ruleDto, RuleMapper mapper) {
+    mapper.deleteRuleDefaultImpacts(ruleDto.getUuid());
+    insertRuleDefaultImpacts(ruleDto, mapper);
+  }
+
+  private static void insertRuleDefaultImpacts(RuleDto ruleDto, RuleMapper mapper) {
+    ruleDto.getDefaultImpacts()
+      .forEach(section -> mapper.insertRuleDefaultImpact(ruleDto.getUuid(), section));
+  }
+
   public void scrollIndexingRuleExtensionsByIds(DbSession dbSession, Collection<String> ruleExtensionIds, Consumer<RuleExtensionForIndexingDto> consumer) {
     RuleMapper mapper = mapper(dbSession);
 
index ba3d01d4aa7146095dbe6bc5d5d779e36dd3a22f..b01a42201692ff0ebc6adfd37f07c8513d558eb5 100644 (file)
@@ -27,16 +27,20 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.sonar.api.issue.impact.SoftwareQuality;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rules.CleanCodeAttribute;
 import org.sonar.api.rules.RuleType;
+import org.sonar.db.issue.ImpactDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptySet;
 import static java.util.Optional.ofNullable;
@@ -68,6 +72,9 @@ public class RuleDto {
    */
   private RuleDto.Format descriptionFormat = null;
   private RuleStatus status = null;
+
+  private Set<ImpactDto> defaultImpacts = new HashSet<>();
+
   private String name = null;
   private String configKey = null;
 
@@ -267,6 +274,29 @@ public class RuleDto {
     return this;
   }
 
+  public Set<ImpactDto> getDefaultImpacts() {
+    return defaultImpacts;
+  }
+
+  public RuleDto addDefaultImpact(ImpactDto defaultImpactDto) {
+    defaultImpacts.stream().filter(impactDto -> impactDto.getSoftwareQuality() == defaultImpactDto.getSoftwareQuality()).findFirst()
+      .ifPresent(impactDto -> {
+        throw new IllegalStateException(format("Impact already defined on rule for Software Quality [%s]", defaultImpactDto.getSoftwareQuality()));
+      });
+    defaultImpacts.add(defaultImpactDto);
+    return this;
+  }
+
+  public RuleDto replaceAllDefaultImpacts(Collection<ImpactDto> newImpacts) {
+    Set<SoftwareQuality> newSoftwareQuality = newImpacts.stream().map(ImpactDto::getSoftwareQuality).collect(Collectors.toSet());
+    if (newSoftwareQuality.size() != newImpacts.size()) {
+      throw new IllegalStateException("Impacts must have unique Software Quality values");
+    }
+    defaultImpacts.clear();
+    defaultImpacts.addAll(newImpacts);
+    return this;
+  }
+
   public String getName() {
     return name;
   }
@@ -424,7 +454,6 @@ public class RuleDto {
     return this;
   }
 
-
   @CheckForNull
   public String getDefRemediationFunction() {
     return defRemediationFunction;
index 1deca591f7de2f8ab384ae1b694e762b963b185c..5595860cad9cefe08e8db959185659ef56d8a1b0 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Set;
 import org.apache.ibatis.annotations.Param;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.RuleQuery;
+import org.sonar.db.issue.ImpactDto;
 
 public interface RuleMapper {
 
@@ -52,6 +53,8 @@ public interface RuleMapper {
 
   void insertRuleDescriptionSection(@Param("ruleUuid") String ruleUuid, @Param("dto") RuleDescriptionSectionDto ruleDescriptionSectionDto);
 
+  void insertRuleDefaultImpact(@Param("ruleUuid") String ruleUuid, @Param("dto") ImpactDto ruleDefaultImpactDto);
+
   void updateRule(RuleDto ruleDefinitionDto);
 
   void deleteRuleDescriptionSection(String ruleUuid);
@@ -77,4 +80,6 @@ public interface RuleMapper {
   void deleteDeprecatedRuleKeys(@Param("uuids") List<String> uuids);
 
   void insertDeprecatedRuleKey(DeprecatedRuleKeyDto deprecatedRuleKeyDto);
+
+  void deleteRuleDefaultImpacts(String ruleUuid);
 }
index 0a6bb0f7a4151c6210a6ac32e5a98195ac8119ad..e1966bf9aa628d630c6ba21e9c6769ec00f44212 100644 (file)
@@ -41,6 +41,8 @@
     i.issue_type as type,
     i.quick_fix_available as quickFixAvailable,
     i.code_variants as codeVariantsString,
+    <include refid="issueImpactsColumns"/>
+    <include refid="ruleDefaultImpactsColumns"/>
     <include refid="isNewCodeReferenceIssue"/>
   </sql>
 
     </if>
   </sql>
 
+  <sql id="issueImpactsColumns">
+    ii.uuid as "ii_uuid",
+    ii.software_quality as "ii_softwareQuality",
+    ii.severity as "ii_severity",
+  </sql>
+  <sql id="ruleDefaultImpactsColumns">
+    rdi.uuid as "rdi_uuid",
+    rdi.software_quality as "rdi_softwareQuality",
+    rdi.severity as "rdi_severity",
+  </sql>
+
+  <resultMap id="issueResultMap" type="Issue" autoMapping="true">
+    <id property="kee" column="kee"/>
+
+    <collection property="impacts" column="ii_uuid" notNullColumn="ii_uuid"
+                javaType="java.util.Set" ofType="Impact">
+      <id property="uuid" column="ii_uuid"/>
+      <result property="softwareQuality" column="ii_softwareQuality"/>
+      <result property="severity" column="ii_severity"/>
+    </collection>
+    <collection property="ruleDefaultImpacts" column="rdi_uuid" notNullColumn="rdi_uuid"
+                    javaType="java.util.Set" ofType="Impact">
+      <id property="uuid" column="rdi_uuid"/>
+      <result property="softwareQuality" column="rdi_softwareQuality"/>
+      <result property="severity" column="rdi_severity"/>
+    </collection>
+  </resultMap>
+
   <insert id="insert" parameterType="Issue" useGeneratedKeys="false">
     INSERT INTO issues (kee, rule_uuid, severity, manual_severity,
     message, message_formattings, line, locations, gap, effort, status, tags, rule_description_context_key,
     where kee = #{kee} and updated_at &lt;= #{selectedAt}
   </update>
 
-  <select id="selectByKey" parameterType="String" resultType="Issue">
+  <select id="selectByKey" parameterType="String" resultMap="issueResultMap">
     select
     <include refid="issueColumns"/>,
     u.login as assigneeLogin
     where i.kee=#{kee,jdbcType=VARCHAR}
   </select>
 
-  <select id="scrollNonClosedByComponentUuid" parameterType="String" resultType="Issue" fetchSize="${_scrollFetchSize}"
-          resultSetType="FORWARD_ONLY">
+  <select id="scrollNonClosedByComponentUuid" parameterType="String" resultMap="issueResultMap" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY" resultOrdered="true">
     select
     <include refid="issueColumns"/>
     from issues i
     where
     i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and
     i.status &lt;&gt; 'CLOSED'
+    order by
+      i.kee
   </select>
 
-  <select id="scrollClosedByComponentUuid" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
+  <select id="scrollClosedByComponentUuid" resultMap="issueResultMap" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY" resultOrdered="true">
     select
       <include refid="issueColumns"/>,
       ic.change_data as closedChangeData
     where i.project_uuid=#{projectUuid,jdbcType=VARCHAR} and i.status &lt;&gt; 'CLOSED'
   </select>
 
-  <select id="selectByKeys" parameterType="map" resultType="Issue">
+  <select id="selectByKeys" parameterType="map" resultMap="issueResultMap">
     select
     <include refid="issueColumns"/>
     from issues i
     </foreach>
   </select>
 
-  <select id="selectByKeysIfNotUpdatedAt" parameterType="map" resultType="Issue">
+  <select id="selectByKeysIfNotUpdatedAt" parameterType="map" resultMap="issueResultMap">
     select
       <include refid="issueColumns"/>
     from issues i
            order by i.rn asc
   </select>
 
-  <select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue">
+  <select id="selectByComponentUuidPaginated" parameterType="map" resultMap="issueResultMap">
     select
     <include refid="issueColumns"/>,
     u.login as assigneeLogin
     limit #{pagination.pageSize,jdbcType=INTEGER} offset #{pagination.offset,jdbcType=INTEGER}
   </select>
 
-  <select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue" databaseId="mssql">
+  <select id="selectByComponentUuidPaginated" parameterType="map" resultMap="issueResultMap" databaseId="mssql">
        select
     <include refid="issueColumns"/>,
     u.login as assigneeLogin
     left outer join rules_default_impacts rdi on r.uuid = rdi.rule_uuid
   </select>
 
-   <select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue" databaseId="oracle">
+   <select id="selectByComponentUuidPaginated" parameterType="map" resultMap="issueResultMap" databaseId="oracle">
     select
     <include refid="issueColumns"/>,
     u.login as assigneeLogin
index 1e7702bde2226c4649f391eb0a9da608323ae536..e29021fe997d7f74e76cff6f7920e4ce0abbf734 100644 (file)
       )
   </delete>
 
+  <delete id="deleteIssuesImpactsByProjectUuid" parameterType="map">
+    delete from issues_impacts
+    where
+      issue_key in (
+        select
+          kee
+        from issues
+        where
+          project_uuid = #{projectUuid,jdbcType=VARCHAR}
+      )
+  </delete>
+
   <delete id="deleteFileSourcesByProjectUuid">
     delete from file_sources where project_uuid=#{rootProjectUuid,jdbcType=VARCHAR}
   </delete>
     </foreach>
   </delete>
 
+  <delete id="deleteIssuesImpactsFromKeys" parameterType="map">
+    DELETE FROM issues_impacts
+    WHERE issue_key IN
+    <foreach collection="issueKeys" open="(" close=")" item="issueKey" separator=",">
+      #{issueKey,jdbcType=VARCHAR}
+    </foreach>
+  </delete>
+
   <delete id="deleteCeScannerContextOfCeActivityByRootUuidOrBefore">
     delete from ce_scanner_context
     where
index 953157fac2974cbb7a1ad73a3ad89dbdc666aaec..e0518e7009d7ec971ec194ad7b7405e27f7a948b 100644 (file)
       rds.rule_uuid = r.uuid
   </sql>
 
+  <sql id="leftOuterJoinRulesDefaultImpacts">
+    left outer join rules_default_impacts rdi on
+      rdi.rule_uuid = r.uuid
+  </sql>
+
   <sql id="selectJoinedTablesColumns">
+    <!-- rule default impacts -->
+    rdi.uuid as "rdi_uuid",
+    rdi.rule_uuid as "rdi_ruleUuid",
+    rdi.software_quality as "rdi_softwareQuality",
+    rdi.severity as "rdi_severity",
+    <!-- rule description sections -->
     rds.content as "rds_content",
     rds.uuid as "rds_uuid",
     rds.kee as "rds_kee",
     rds.context_key as "rds_contextKey",
     rds.context_display_name as "rds_contextDisplayName",
+    <!-- rule -->
     r.uuid as "r_uuid",
-
     <include refid="ruleColumns"/>
   </sql>
 
@@ -65,6 +76,8 @@
     from
       rules r
     <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultImpacts"/>
+    order by r.uuid
   </select>
 
   <resultMap id="ruleResultMap" type="org.sonar.db.rule.RuleDto" autoMapping="true">
         </constructor>
       </association>
     </collection>
+    <collection property="defaultImpacts" column="rdi_uuid" notNullColumn="rdi_uuid" javaType="java.util.Set" ofType="Impact">
+     <id property="uuid" column="rdi_uuid"/>
+     <result property="softwareQuality" column="rdi_softwareQuality"/>
+     <result property="severity" column="rdi_severity"/>
+    </collection>
   </resultMap>
 
-
-
   <select id="selectEnabled" resultMap="ruleResultMap">
     select
       <include refid="selectJoinedTablesColumns"/>
     from
       rules r
     <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultImpacts"/>
     where
       r.status != 'REMOVED'
+    order by r.uuid
   </select>
 
   <select id="selectByUuid" parameterType="map" resultMap="ruleResultMap">
     from
       rules r
     <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultImpacts"/>
     where
       r.uuid=#{uuid,jdbcType=VARCHAR}
+    order by r.uuid
   </select>
 
   <select id="selectByUuids" parameterType="map" resultMap="ruleResultMap">
     from
       rules r
     <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultImpacts"/>
     where
       <foreach collection="uuids" index="index" item="uuid" open="" separator=" or " close="">
         r.uuid=#{uuid,jdbcType=VARCHAR}
       </foreach>
+    order by r.uuid
   </select>
 
   <select id="selectByKey" parameterType="map" resultMap="ruleResultMap">
     from
       rules r
     <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultImpacts"/>
     where
       r.plugin_name=#{ruleKey.repository,jdbcType=VARCHAR}
       and r.plugin_rule_key=#{ruleKey.rule,jdbcType=VARCHAR}
+    order by r.uuid
   </select>
 
   <select id="selectIndexingRuleExtensionsByIds" parameterType="map" resultType="org.sonar.db.rule.RuleExtensionForIndexingDto">
     from
       rules r
     <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultImpacts"/>
     where
       <foreach collection="ruleKeys" index="index" item="ruleKey" open="" separator=" or " close="">
         (r.plugin_name=#{ruleKey.repository,jdbcType=VARCHAR} and r.plugin_rule_key=#{ruleKey.rule,jdbcType=VARCHAR})
       </foreach>
+    order by r.uuid
   </select>
 
   <select id="selectByQuery" parameterType="map" resultMap="ruleResultMap">
     from
       rules r
     <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultImpacts"/>
     where
       r.status != 'REMOVED'
       <if test="query.repositoryKey!=null">
         and r.plugin_config_key = #{query.configKey,jdbcType=VARCHAR}
       </if>
     order by
+      r.uuid,
       r.updated_at desc
   </select>
 
     from
     rules r
     <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultImpacts"/>
     where
       r.status != 'REMOVED' and r.is_external=${_false} and r.is_template=${_false}
       and r.rule_type in
     <foreach collection="types" item="type" separator="," open="(" close=")">#{type, jdbcType=INTEGER}</foreach>
       and r.language in
     <foreach collection="languages" item="language" separator="," open="(" close=")">#{language, jdbcType=VARCHAR}</foreach>
+    order by r.uuid
   </select>
 
   <select id="selectByLanguage" parameterType="String" resultType="org.sonar.db.rule.RuleDto" fetchSize="${_scrollFetchSize}"
     )
   </insert>
 
+  <insert id="insertRuleDefaultImpact" parameterType="Map" useGeneratedKeys="false">
+    INSERT INTO rules_default_impacts (uuid, rule_uuid, software_quality, severity)
+    VALUES (
+    #{dto.uuid,jdbcType=VARCHAR},
+    #{ruleUuid,jdbcType=VARCHAR},
+    #{dto.softwareQuality,jdbcType=VARCHAR},
+    #{dto.severity,jdbcType=VARCHAR})
+  </insert>
+
   <update id="updateRule" parameterType="org.sonar.db.rule.RuleDto">
     update rules set
       plugin_key=#{pluginKey,jdbcType=VARCHAR},
       rule_uuid=#{ruleUuid,jdbcType=VARCHAR}
   </delete>
 
+  <delete id="deleteRuleDefaultImpacts" parameterType="String">
+    delete from
+      rules_default_impacts
+    where
+      rule_uuid=#{ruleUuid,jdbcType=VARCHAR}
+  </delete>
+
   <delete id="deleteParams" parameterType="String">
     delete from
       active_rule_parameters
index 766d78fcf50b863917b8e041a404883d9019e560..66f510cb5a2d72307131b4065755d16de767b8c5 100644 (file)
@@ -29,15 +29,20 @@ import java.util.Set;
 import org.apache.commons.lang.time.DateUtils;
 import org.junit.Test;
 import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
 import org.sonar.api.rule.RuleKey;
 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.UuidFactoryFast;
 import org.sonar.db.protobuf.DbIssues;
 import org.sonar.db.rule.RuleDto;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
 
 public class IssueDtoTest {
 
@@ -153,6 +158,83 @@ public class IssueDtoTest {
     assertThat(dto.getTags()).isEmpty();
   }
 
+  @Test
+  public void getEffectiveImpacts_whenNoIssueImpactsOverridden_shouldReturnRuleImpacts() {
+    IssueDto dto = new IssueDto();
+    dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
+    dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.SECURITY, Severity.MEDIUM));
+    dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));
+
+    assertThat(dto.getEffectiveImpacts())
+      .containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
+      .containsEntry(SoftwareQuality.SECURITY, Severity.MEDIUM)
+      .containsEntry(SoftwareQuality.RELIABILITY, Severity.LOW);
+  }
+
+  @Test
+  public void getEffectiveImpacts_whenIssueImpactsOverridden_shouldReturnRuleImpactsOverriddenByIssueImpacts() {
+    IssueDto dto = new IssueDto();
+    dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
+    dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.SECURITY, Severity.MEDIUM));
+    dto.getRuleDefaultImpacts().add(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));
+
+    dto.addImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
+    dto.addImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.HIGH));
+
+    assertThat(dto.getEffectiveImpacts())
+      .containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.LOW)
+      .containsEntry(SoftwareQuality.SECURITY, Severity.MEDIUM)
+      .containsEntry(SoftwareQuality.RELIABILITY, Severity.HIGH);
+  }
+
+  @Test
+  public void addImpact_whenSoftwareQualityAlreadyDefined_shouldThrowISE() {
+    IssueDto dto = new IssueDto();
+    dto.addImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
+
+    ImpactDto duplicatedImpact = newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
+
+    assertThatThrownBy(() -> dto.addImpact(duplicatedImpact))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Impact already defined on issue for Software Quality [MAINTAINABILITY]");
+  }
+
+  @Test
+  public void replaceAllImpacts_whenSoftwareQualityAlreadyDuplicated_shouldThrowISE() {
+    IssueDto dto = new IssueDto();
+    dto.addImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
+    dto.addImpact(newImpactDto(SoftwareQuality.SECURITY, Severity.HIGH));
+    dto.addImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));
+
+    Set<ImpactDto> duplicatedImpacts = Set.of(
+      newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
+      newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
+    assertThatThrownBy(() -> dto.replaceAllImpacts(duplicatedImpacts))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Impacts must have unique Software Quality values");
+  }
+
+  @Test
+  public void replaceAllImpacts_shouldReplaceExistingImpacts() {
+    IssueDto dto = new IssueDto();
+    dto.addImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
+    dto.addImpact(newImpactDto(SoftwareQuality.SECURITY, Severity.HIGH));
+    dto.addImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));
+
+    Set<ImpactDto> duplicatedImpacts = Set.of(
+      newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
+      newImpactDto(SoftwareQuality.SECURITY, Severity.LOW));
+
+    dto.replaceAllImpacts(duplicatedImpacts);
+
+    assertThat(dto.getImpacts())
+      .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+      .containsExactlyInAnyOrder(
+        tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
+        tuple(SoftwareQuality.SECURITY, Severity.LOW));
+
+  }
+
   @Test
   public void setCodeVariants_shouldReturnCodeVariants() {
     IssueDto dto = new IssueDto();
@@ -279,4 +361,12 @@ public class IssueDtoTest {
       .setCodeVariants(List.of("variant1", "variant2"));
     return defaultIssue;
   }
+
+  public static ImpactDto newImpactDto(SoftwareQuality softwareQuality, Severity severity) {
+    return new ImpactDto()
+      .setUuid(UuidFactoryFast.getInstance().create())
+      .setSoftwareQuality(softwareQuality)
+      .setSeverity(severity);
+  }
+
 }
index a6aec187b52811a38574bc2bc3f9413405b207f5..aca125d4708d58d6096e4c54b0023e6101c864f1 100644 (file)
@@ -25,19 +25,24 @@ import java.util.Set;
 import java.util.TreeSet;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.core.util.UuidFactoryFast;
 import org.sonar.core.util.Uuids;
+import org.sonar.db.issue.ImpactDto;
 
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.apache.commons.lang.StringUtils.repeat;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
 import static org.sonar.db.rule.RuleDto.ERROR_MESSAGE_SECTION_ALREADY_EXISTS;
 import static org.sonar.db.rule.RuleTesting.newRule;
 
 public class RuleDtoTest {
 
-
   public static final String SECTION_KEY = "section key";
+
   @Test
   public void fail_if_key_is_too_long() {
     assertThatThrownBy(() -> new RuleDto().setRuleKey(repeat("x", 250)))
@@ -164,6 +169,54 @@ public class RuleDtoTest {
 
   }
 
+  @Test
+  public void addDefaultImpact_whenSoftwareQualityAlreadyDefined_shouldThrowISE() {
+    RuleDto dto = new RuleDto();
+    dto.addDefaultImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
+
+    ImpactDto duplicatedImpact = newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
+
+    assertThatThrownBy(() -> dto.addDefaultImpact(duplicatedImpact))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Impact already defined on rule for Software Quality [MAINTAINABILITY]");
+  }
+
+  @Test
+  public void replaceAllDefaultImpacts_whenSoftwareQualityAlreadyDuplicated_shouldThrowISE() {
+    RuleDto dto = new RuleDto();
+    dto.addDefaultImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
+    dto.addDefaultImpact(newImpactDto(SoftwareQuality.SECURITY, Severity.HIGH));
+    dto.addDefaultImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));
+
+    Set<ImpactDto> duplicatedImpacts = Set.of(
+      newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
+      newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
+    assertThatThrownBy(() -> dto.replaceAllDefaultImpacts(duplicatedImpacts))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Impacts must have unique Software Quality values");
+  }
+
+  @Test
+  public void replaceAllImpacts_shouldReplaceExistingImpacts() {
+    RuleDto dto = new RuleDto();
+    dto.addDefaultImpact(newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
+    dto.addDefaultImpact(newImpactDto(SoftwareQuality.SECURITY, Severity.HIGH));
+    dto.addDefaultImpact(newImpactDto(SoftwareQuality.RELIABILITY, Severity.LOW));
+
+    Set<ImpactDto> duplicatedImpacts = Set.of(
+      newImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
+      newImpactDto(SoftwareQuality.SECURITY, Severity.LOW));
+
+    dto.replaceAllDefaultImpacts(duplicatedImpacts);
+
+    assertThat(dto.getDefaultImpacts())
+      .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+      .containsExactlyInAnyOrder(
+        tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH),
+        tuple(SoftwareQuality.SECURITY, Severity.LOW));
+
+  }
+
   @NotNull
   private static RuleDescriptionSectionDto createSection(String section_key, String contextKey, String contextDisplayName) {
     return RuleDescriptionSectionDto.builder()
@@ -178,4 +231,11 @@ public class RuleDtoTest {
       .key(section_key)
       .build();
   }
+
+  public static ImpactDto newImpactDto(SoftwareQuality softwareQuality, Severity severity) {
+    return new ImpactDto()
+      .setUuid(UuidFactoryFast.getInstance().create())
+      .setSoftwareQuality(softwareQuality)
+      .setSeverity(severity);
+  }
 }
index 7fbae9e48d548f7e6401b6e76885257f912b6701..4e376373f5a193e181423832b7a2faed3acd4509 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import javax.annotation.Nullable;
+import org.sonar.api.issue.impact.SoftwareQuality;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rule.Severity;
@@ -31,6 +32,7 @@ import org.sonar.api.rules.RuleType;
 import org.sonar.api.server.rule.RuleParamType;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.issue.ImpactDto;
 import org.sonar.db.rule.RuleDto.Scope;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -62,10 +64,10 @@ public class RuleTesting {
     // only static helpers
   }
 
-
   public static RuleDto newRule() {
     return newRule(RuleKey.of(randomAlphanumeric(30), randomAlphanumeric(30)));
   }
+
   public static RuleDto newRule(RuleDescriptionSectionDto... ruleDescriptionSectionDtos) {
     return newRule(randomRuleKey(), ruleDescriptionSectionDtos);
   }
@@ -94,6 +96,9 @@ public class RuleTesting {
       .setDescriptionFormat(RuleDto.Format.HTML)
       .setType(CODE_SMELL)
       .setCleanCodeAttribute(CleanCodeAttribute.CLEAR)
+      .addDefaultImpact(new ImpactDto().setUuid(uuidFactory.create())
+        .setSoftwareQuality(SoftwareQuality.MAINTAINABILITY)
+        .setSeverity(org.sonar.api.issue.impact.Severity.HIGH))
       .setStatus(RuleStatus.READY)
       .setConfigKey("configKey_" + ruleKey.rule())
       .setSeverity(Severity.ALL.get(nextInt(Severity.ALL.size())))
@@ -104,7 +109,7 @@ public class RuleTesting {
       .setLanguage("lang_" + randomAlphanumeric(3))
       .setGapDescription("gapDescription_" + randomAlphanumeric(5))
       .setDefRemediationBaseEffort(nextInt(10) + "h")
-      //voluntarily offset the remediation to be able to detect issues
+      // voluntarily offset the remediation to be able to detect issues
       .setDefRemediationGapMultiplier((nextInt(10) + 10) + "h")
       .setDefRemediationFunction("LINEAR_OFFSET")
       .setRemediationBaseEffort(nextInt(10) + "h")
@@ -151,7 +156,6 @@ public class RuleTesting {
     return newRule(XOO_X2).setLanguage("xoo");
   }
 
-
   public static RuleDto newTemplateRule(RuleKey ruleKey) {
     return newRule(ruleKey)
       .setIsTemplate(true);