]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23250 Initial population of active_rules with impacts
authorDejan Milisavljevic <dejan.milisavljevic@sonarsource.com>
Thu, 10 Oct 2024 14:21:04 +0000 (16:21 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Oct 2024 20:03:01 +0000 (20:03 +0000)
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml
server/sonar-webserver-core/build.gradle
server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/ActiveRulesImpactInitializer.java [new file with mode: 0644]
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java

index 4e6501a043d4402717d61da9429f720ce7b4a46c..e085a3cc315c463ed53d49392af6df5c11aa0326 100644 (file)
@@ -68,6 +68,10 @@ public class ActiveRuleDao implements Dao {
     return mapper(dbSession).selectByRuleUuid(ruleUuid);
   }
 
+  public List<ActiveRuleDto> selectByRepository(DbSession dbSession, String repositoryKey, String repositoryLanguage) {
+    return mapper(dbSession).selectByRepository(repositoryKey, repositoryLanguage);
+  }
+
   public List<OrgActiveRuleDto> selectByRuleUuids(DbSession dbSession, List<String> uuids) {
     return executeLargeInputs(uuids, chunk -> mapper(dbSession).selectByRuleUuids(chunk));
   }
index 1e276615f3791393538e6172827fcdaeb1e6187c..d2b2dddfdbe4116b66cddac6cd8c3f17dc9c5763 100644 (file)
@@ -47,6 +47,9 @@ public interface ActiveRuleMapper {
 
   List<ActiveRuleDto> selectByRuleUuid(String ruleUuid);
 
+  List<ActiveRuleDto> selectByRepository(@Param("repositoryKey") String repositoryKey,
+    @Param("repositoryLanguage") String repositoryLanguage);
+
   List<OrgActiveRuleDto> selectByRuleUuids(@Param("ruleUuids") List<String> partitionOfRuleUuids);
 
   List<OrgActiveRuleDto> selectByProfileUuid(String uuid);
index 92a9b7fca53738a2e0fa1c763d1e1acc65430a1d..104428585289e1bcec4634b478c97667ec4f1f52 100644 (file)
       r.uuid = #{ruleUuid, jdbcType=VARCHAR}
   </select>
 
+  <select id="selectByRepository" parameterType="String" resultType="ActiveRule">
+    select
+    <include refid="activeRuleColumns"/>
+    from active_rules a
+    <include refid="activeRuleKeyJoin"/>
+    where
+      r.plugin_name = #{repositoryKey, jdbcType=VARCHAR}
+      and r.language = #{repositoryLanguage, jdbcType=VARCHAR}
+  </select>
+
   <select id="selectByRuleUuids" parameterType="List" resultType="org.sonar.db.qualityprofile.OrgActiveRuleDto">
     select
     <include refid="orgActiveRuleColumns"/>
index ecdf1cac6521c939f9e72faa79c6d0747e72cb7a..79e113b5d290cc191b8be7b1c7324d386f871dc1 100644 (file)
@@ -68,6 +68,7 @@ dependencies {
   testImplementation 'org.eclipse.jetty:jetty-servlet'
   testImplementation 'org.hamcrest:hamcrest'
   testImplementation 'org.junit.jupiter:junit-jupiter-api'
+  testImplementation 'org.junit.jupiter:junit-jupiter-params'
   testImplementation 'org.mockito:mockito-core'
   testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures'
   testImplementation 'org.subethamail:subethasmtp'
index b11ba508409b34f1e1dcb77806b06659183764fc..1cc61bd147f8d7f1a11230deaa2e7ecb8c2f867d 100644 (file)
@@ -19,9 +19,6 @@
  */
 package org.sonar.server.rule.registration;
 
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
@@ -33,9 +30,11 @@ import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.elasticsearch.common.util.set.Sets;
 import org.jetbrains.annotations.Nullable;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.issue.impact.Severity;
 import org.sonar.api.issue.impact.SoftwareQuality;
@@ -48,7 +47,6 @@ import org.sonar.api.server.debt.DebtRemediationFunction;
 import org.sonar.api.server.rule.Context;
 import org.sonar.api.server.rule.RuleDescriptionSection;
 import org.sonar.api.server.rule.RulesDefinition;
-import org.sonar.api.testfixtures.log.LogTester;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.Version;
 import org.sonar.core.platform.SonarQubeVersion;
@@ -62,6 +60,7 @@ import org.sonar.db.qualityprofile.ActiveRuleDto;
 import org.sonar.db.qualityprofile.QProfileChangeDto;
 import org.sonar.db.qualityprofile.QProfileChangeQuery;
 import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.qualityprofile.RulesProfileDto;
 import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDescriptionSectionContextDto;
 import org.sonar.db.rule.RuleDescriptionSectionDto;
@@ -69,12 +68,16 @@ import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleDto.Scope;
 import org.sonar.db.rule.RuleParamDto;
 import org.sonar.db.rule.RuleRepositoryDto;
+import org.sonar.db.rule.RuleTesting;
+import org.sonar.db.rule.SeverityUtil;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchIdResult;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.es.metadata.MetadataIndex;
 import org.sonar.server.plugins.DetectPluginChange;
 import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.property.InternalProperties;
+import org.sonar.server.property.MapInternalProperties;
 import org.sonar.server.qualityprofile.ActiveRuleChange;
 import org.sonar.server.qualityprofile.QProfileRules;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
@@ -121,8 +124,7 @@ import static org.sonar.db.rule.RuleDescriptionSectionDto.builder;
 import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
 
-@RunWith(DataProviderRunner.class)
-public class RulesRegistrantIT {
+class RulesRegistrantIT {
 
   private static final String FAKE_PLUGIN_KEY = "unittest";
   private static final Date DATE1 = DateUtils.parseDateTime("2014-01-01T19:10:03+0100");
@@ -139,12 +141,10 @@ public class RulesRegistrantIT {
 
   private final TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
 
-  @org.junit.Rule
-  public DbTester db = DbTester.create(system);
-  @org.junit.Rule
-  public EsTester es = EsTester.create();
-  @org.junit.Rule
-  public LogTester logTester = new LogTester();
+  @RegisterExtension
+  private final DbTester db = DbTester.create(system);
+  @RegisterExtension
+  private final EsTester es = EsTester.create();
 
   private final QProfileRules qProfileRules = mock();
   private final WebServerRuleFinder webServerRuleFinder = mock();
@@ -153,9 +153,11 @@ public class RulesRegistrantIT {
   private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
   private final SonarQubeVersion sonarQubeVersion = new SonarQubeVersion(Version.create(10, 3));
 
+  private InternalProperties internalProperties;
   private RuleIndexer ruleIndexer;
   private ActiveRuleIndexer activeRuleIndexer;
   private RuleIndex ruleIndex;
+  private ActiveRulesImpactInitializer activeRulesImpactInitializer;
   private final RuleDescriptionSectionsGenerator ruleDescriptionSectionsGenerator = mock();
   private final RuleDescriptionSectionsGeneratorResolver resolver = mock();
 
@@ -165,11 +167,13 @@ public class RulesRegistrantIT {
   private final QualityProfileChangesUpdater qualityProfileChangesUpdater = mock();
   private final DetectPluginChange detectPluginChange = mock();
 
-  @Before
-  public void before() {
+  @BeforeEach
+  void before() {
     ruleIndexer = new RuleIndexer(es.client(), dbClient);
     ruleIndex = new RuleIndex(es.client(), system);
     activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client());
+    internalProperties = new MapInternalProperties();
+    activeRulesImpactInitializer = new ActiveRulesImpactInitializer(internalProperties, dbClient);
     when(resolver.generateFor(any())).thenAnswer(answer -> {
       RulesDefinition.Rule rule = answer.getArgument(0, RulesDefinition.Rule.class);
       String description = rule.htmlDescription() == null ? rule.markdownDescription() : rule.htmlDescription();
@@ -190,7 +194,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void insert_new_rules() {
+  void insert_new_rules() {
     executeWithPluginRules(new FakeRepositoryV1());
 
     // verify db
@@ -236,7 +240,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void insert_new_external_rule() {
+  void insert_new_external_rule() {
     executeWithPluginRules(new ExternalRuleRepository());
 
     // verify db
@@ -270,8 +274,8 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void insert_then_remove_rule() {
-    String ruleKey = secure().nextAlphanumeric(5);
+  void insert_then_remove_rule() {
+    String ruleKey = secure().randomAlphanumeric(5);
 
     // register one rule
     executeWithPluginRules(context -> {
@@ -314,7 +318,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void mass_insert_then_remove_rule() {
+  void mass_insert_then_remove_rule() {
     int numberOfRules = 5000;
 
     // register many rules
@@ -355,7 +359,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void delete_repositories_that_have_been_uninstalled() {
+  void delete_repositories_that_have_been_uninstalled() {
     RuleRepositoryDto repository = new RuleRepositoryDto("findbugs", "java", "Findbugs");
     DbSession dbSession = db.getSession();
     db.getDbClient().ruleRepositoryDao().insert(dbSession, singletonList(repository));
@@ -367,7 +371,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void update_and_remove_rules_on_changes() {
+  void update_and_remove_rules_on_changes() {
     executeWithPluginRules(new FakeRepositoryV1());
     assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
@@ -440,7 +444,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void add_new_tag() {
+  void add_new_tag() {
     executeWithPluginRules(context -> {
       NewRepository repo = context.createRepository("fake", "java");
       repo.createRule("rule1")
@@ -467,7 +471,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void add_new_security_standards() {
+  void add_new_security_standards() {
     executeWithPluginRules(context -> {
       NewRepository repo = context.createRepository("fake", "java");
       repo.createRule("rule1")
@@ -496,7 +500,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void update_only_rule_name() {
+  void update_only_rule_name() {
     system.setNow(DATE1.getTime());
     executeWithPluginRules(context -> {
       NewRepository repo = context.createRepository("fake", "java");
@@ -525,7 +529,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void update_template_rule_key_should_also_update_custom_rules() {
+  void update_template_rule_key_should_also_update_custom_rules() {
     system.setNow(DATE1.getTime());
     executeWithPluginRules(context -> {
       NewRepository repo = context.createRepository("squid", "java");
@@ -564,7 +568,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void update_if_rule_key_renamed_and_deprecated_key_declared() {
+  void update_if_rule_key_renamed_and_deprecated_key_declared() {
     String ruleKey1 = "rule1";
     String ruleKey2 = "rule2";
     String repository = "fake";
@@ -606,7 +610,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void update_if_repository_changed_and_deprecated_key_declared() {
+  void update_if_repository_changed_and_deprecated_key_declared() {
     String ruleKey = "rule";
     String repository1 = "fake1";
     String repository2 = "fake2";
@@ -647,9 +651,9 @@ public class RulesRegistrantIT {
     assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
   }
 
-  @Test
-  @UseDataProvider("allRenamingCases")
-  public void update_if_only_renamed_and_deprecated_key_declared(String ruleKey1, String repo1, String ruleKey2, String repo2) {
+  @ParameterizedTest
+  @MethodSource("allRenamingCases")
+  void update_if_only_renamed_and_deprecated_key_declared(String ruleKey1, String repo1, String ruleKey2, String repo2) {
     String name = "Name1";
     String description = "Description";
     system.setNow(DATE1.getTime());
@@ -685,8 +689,7 @@ public class RulesRegistrantIT {
       .containsOnly(rule2.getUuid());
   }
 
-  @DataProvider
-  public static Object[][] allRenamingCases() {
+  private static Object[][] allRenamingCases() {
     return new Object[][]{
       {"repo1", "rule1", "repo1", "rule2"},
       {"repo1", "rule1", "repo2", "rule1"},
@@ -695,7 +698,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void update_if_repository_and_key_changed_and_deprecated_key_declared_among_others() {
+  void update_if_repository_and_key_changed_and_deprecated_key_declared_among_others() {
     String ruleKey1 = "rule1";
     String ruleKey2 = "rule2";
     String repository1 = "fake1";
@@ -735,7 +738,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void update_only_rule_description() {
+  void update_only_rule_description() {
     system.setNow(DATE1.getTime());
     executeWithPluginRules(context -> {
       NewRepository repo = context.createRepository("fake", "java");
@@ -764,7 +767,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void update_several_rule_descriptions() {
+  void update_several_rule_descriptions() {
     system.setNow(DATE1.getTime());
 
     RuleDescriptionSection section1context1 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx1 content", "ctx_1");
@@ -848,7 +851,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void rule_previously_created_as_adhoc_becomes_none_adhoc() {
+  void rule_previously_created_as_adhoc_becomes_none_adhoc() {
     RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake").setIsExternal(true).setIsAdHoc(true));
     system.setNow(DATE2.getTime());
     executeWithPluginRules(context -> {
@@ -864,7 +867,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void remove_no_more_defined_external_rule() {
+  void remove_no_more_defined_external_rule() {
     RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
       .setStatus(READY)
       .setIsExternal(true)
@@ -877,7 +880,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void do_not_remove_no_more_defined_ad_hoc_rule() {
+  void do_not_remove_no_more_defined_ad_hoc_rule() {
     RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
       .setStatus(READY)
       .setIsExternal(true)
@@ -890,7 +893,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void disable_then_enable_rule() {
+  void disable_then_enable_rule() {
     // Install rule
     system.setNow(DATE1.getTime());
     executeWithPluginRules(new FakeRepositoryV1());
@@ -913,7 +916,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void do_not_update_rules_when_no_changes() {
+  void do_not_update_rules_when_no_changes() {
     executeWithPluginRules(new FakeRepositoryV1());
     assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
 
@@ -926,7 +929,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void do_not_update_already_removed_rules() {
+  void do_not_update_already_removed_rules() {
     executeWithPluginRules(new FakeRepositoryV1());
     assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
 
@@ -964,7 +967,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void mass_insert() {
+  void mass_insert() {
     executeWithPluginRules(new BigRepository());
     assertThat(db.countRowsOfTable("rules")).isEqualTo(BigRepository.SIZE);
     assertThat(db.countRowsOfTable("rules_parameters")).isEqualTo(BigRepository.SIZE * 20);
@@ -972,7 +975,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void manage_repository_extensions() {
+  void manage_repository_extensions() {
     executeWithPluginRules(new FindbugsRepository(), new FbContribRepository());
     List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
     assertThat(rules).hasSize(2);
@@ -982,7 +985,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void remove_system_tags_when_plugin_does_not_provide_any() {
+  void remove_system_tags_when_plugin_does_not_provide_any() {
     // Rule already exists in DB, with some system tags
     db.rules().insert(new RuleDto()
       .setRuleKey("rule1")
@@ -1004,7 +1007,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void rules_that_deprecate_previous_rule_must_be_recorded() {
+  void rules_that_deprecate_previous_rule_must_be_recorded() {
     executeWithPluginRules(context -> {
       NewRepository repo = context.createRepository("fake", "java");
       createRule(repo, "rule1");
@@ -1026,7 +1029,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void rules_that_remove_deprecated_key_must_remove_records() {
+  void rules_that_remove_deprecated_key_must_remove_records() {
     executeWithPluginRules(context -> {
       NewRepository repo = context.createRepository("fake", "java");
       createRule(repo, "rule1");
@@ -1057,7 +1060,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void declaring_two_rules_with_same_deprecated_RuleKey_should_throw_ISE() {
+  void declaring_two_rules_with_same_deprecated_RuleKey_should_throw_ISE() {
     assertThatThrownBy(() -> {
       executeWithPluginRules(context -> {
         NewRepository repo = context.createRepository("fake", "java");
@@ -1073,7 +1076,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void declaring_a_rule_with_a_deprecated_RuleKey_still_used_should_throw_ISE() {
+  void declaring_a_rule_with_a_deprecated_RuleKey_still_used_should_throw_ISE() {
     assertThatThrownBy(() -> {
       executeWithPluginRules(context -> {
         NewRepository repo = context.createRepository("fake", "java");
@@ -1088,7 +1091,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void updating_the_deprecated_to_a_new_ruleKey_should_throw_an_ISE() {
+  void updating_the_deprecated_to_a_new_ruleKey_should_throw_an_ISE() {
     // On this new rule add a deprecated key
     executeWithPluginRules(context -> createRule(context, "javascript", "javascript", "s103",
       r -> r.addDeprecatedRuleKey("javascript", "linelength")));
@@ -1104,7 +1107,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void deprecate_rule_that_deprecated_another_rule() {
+  void deprecate_rule_that_deprecated_another_rule() {
     executeWithPluginRules(context -> createRule(context, "javascript", "javascript", "s103"));
     executeWithPluginRules(context -> createRule(context, "javascript", "javascript", "s104",
       r -> r.addDeprecatedRuleKey("javascript", "s103")));
@@ -1116,7 +1119,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void declaring_a_rule_with_an_existing_RuleKey_still_used_should_throw_IAE() {
+  void declaring_a_rule_with_an_existing_RuleKey_still_used_should_throw_IAE() {
     assertThatThrownBy(() -> {
       executeWithPluginRules(context -> {
         NewRepository repo = context.createRepository("fake", "java");
@@ -1130,7 +1133,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void removed_rule_should_appear_in_changelog() {
+  void removed_rule_should_appear_in_changelog() {
     //GIVEN
     QProfileDto qProfileDto = db.qualityProfiles().insert();
     RuleDto ruleDto = db.rules().insert(RULE_KEY1);
@@ -1146,7 +1149,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void removed_rule_should_be_deleted_when_renamed_repository() {
+  void removed_rule_should_be_deleted_when_renamed_repository() {
     //GIVEN
     RuleDto removedRuleDto = db.rules().insert(RuleKey.of("old_repo", "removed_rule"));
     RuleDto renamedRuleDto = db.rules().insert(RuleKey.of("old_repo", "renamed_rule"));
@@ -1158,7 +1161,7 @@ public class RulesRegistrantIT {
   }
 
   @Test
-  public void builtin_rules_should_be_updated_even_if_no_plugin_updates() {
+  void builtin_rules_should_be_updated_even_if_no_plugin_updates() {
     RulesDefinition builtInRepoV1 = context -> createRule(context, "builtin", "sca", "rule1", rule -> rule.setName("Name before"));
     RulesDefinition pluginRepo = context -> createRule(context, "java", "java", "rule2");
 
@@ -1184,6 +1187,134 @@ public class RulesRegistrantIT {
     assertThatCode(() -> dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "rule2"))).doesNotThrowAnyException();
   }
 
+  @Test
+  void impacts_on_active_rules_when_rule_and_active_rule_severity_are_same_should_use_rule_impacts() {
+    RuleDto ruleDto = createAndActivateRuleOnQProfile("rule1", "java", "fake", Severity.LOW, Severity.LOW);
+
+    executeWithPluginRules(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("any")
+        .setHtmlDescription("html")
+        .addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.BLOCKER)
+        .setSeverity(ruleDto.getSeverityString());
+      repo.done();
+    });
+
+    assertThat(internalProperties.read("activeRules.impacts.populated")).isEqualTo(Optional.of("true"));
+
+    List<ActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByRuleUuid(db.getSession(), ruleDto.getUuid());
+    assertThat(activeRules).hasSize(1);
+    assertThat(activeRules.get(0).getImpactsString()).isEqualTo("{\"MAINTAINABILITY\":\"BLOCKER\"}");
+  }
+
+  @Test
+  void impacts_on_active_rules_when_active_rule_severity_changed_should_initialize_with_active_rule_severity() {
+    RuleDto ruleDto = createAndActivateRuleOnQProfile("rule1", "java", "fake", Severity.LOW, Severity.INFO);
+
+    executeWithPluginRules(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("any")
+        .setHtmlDescription("html")
+        .addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.BLOCKER)
+        .setSeverity(SeverityUtil.getSeverityFromOrdinal(2));
+      repo.done();
+    });
+
+    assertThat(internalProperties.read("activeRules.impacts.populated")).isEqualTo(Optional.of("true"));
+
+    List<ActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByRuleUuid(db.getSession(), ruleDto.getUuid());
+    assertThat(activeRules).hasSize(1);
+    assertThat(activeRules.get(0).getImpactsString()).isEqualTo("{\"MAINTAINABILITY\":\"INFO\"}");
+  }
+
+  @Test
+  void impacts_on_active_rules_when_rule_impact_changed_should_use_active_rule_severity() {
+    RuleDto ruleDto = createAndActivateRuleOnQProfile("rule1", "java", "fake", Severity.LOW, Severity.LOW);
+
+    executeWithPluginRules(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("any")
+        .setHtmlDescription("html")
+        .addDefaultImpact(SoftwareQuality.SECURITY, Severity.HIGH)
+        .setSeverity(SeverityUtil.getSeverityFromOrdinal(2));
+      repo.done();
+    });
+
+    assertThat(internalProperties.read("activeRules.impacts.populated")).isEqualTo(Optional.of("true"));
+
+    List<ActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByRuleUuid(db.getSession(), ruleDto.getUuid());
+    assertThat(activeRules).hasSize(1);
+    assertThat(activeRules.get(0).getImpactsString()).isEqualTo("{\"SECURITY\":\"LOW\"}");
+  }
+
+  @Test
+  void impacts_on_active_rules_when_rule_impact_changed_and_active_rule_and_rule_severity_different_should_use_rule_changed_impact() {
+    RuleDto ruleDto = createAndActivateRuleOnQProfile("rule1", "java", "fake", Severity.HIGH, Severity.INFO);
+
+    executeWithPluginRules(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("any")
+        .setHtmlDescription("html")
+        .addDefaultImpact(SoftwareQuality.SECURITY, Severity.LOW)
+        .setSeverity(SeverityUtil.getSeverityFromOrdinal(2));
+      repo.done();
+    });
+
+    assertThat(internalProperties.read("activeRules.impacts.populated")).isEqualTo(Optional.of("true"));
+
+    List<ActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByRuleUuid(db.getSession(), ruleDto.getUuid());
+    assertThat(activeRules).hasSize(1);
+    assertThat(activeRules.get(0).getImpactsString()).isEqualTo("{\"SECURITY\":\"INFO\"}");
+  }
+
+  @Test
+  void impacts_on_active_rules_should_not_be_recomputed_if_completion_flag_is_set() {
+
+    internalProperties.write("activeRules.impacts.populated", Boolean.TRUE.toString());
+
+    RuleDto ruleDto = createAndActivateRuleOnQProfile("rule1", "java", "fake", Severity.LOW, Severity.LOW);
+
+    executeWithPluginRules(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("any")
+        .setHtmlDescription("html");
+      repo.done();
+    });
+
+    List<ActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByRuleUuid(db.getSession(), ruleDto.getUuid());
+    assertThat(activeRules).hasSize(1);
+    assertThat(activeRules.get(0).getImpactsString()).isNull();
+  }
+
+  private RuleDto createAndActivateRuleOnQProfile(String ruleKey, String language, String repositoryKey, Severity ruleSeverity,
+    Severity activeRuleSeverity) {
+    RulesProfileDto rulesProfileDto = new RulesProfileDto()
+      .setUuid(uuidFactory.create())
+      .setName("any");
+    dbClient.qualityProfileDao().insert(db.getSession(), rulesProfileDto);
+
+    RuleDto ruleDto = RuleTesting.newRule()
+      .setRuleKey(ruleKey)
+      .setSeverity(ruleSeverity.ordinal())
+      .replaceAllDefaultImpacts(List.of(new ImpactDto(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)))
+      .setLanguage(language)
+      .setRepositoryKey(repositoryKey);
+    dbClient.ruleDao().insert(db.getSession(), ruleDto);
+
+    ActiveRuleDto activeRuleDto = new ActiveRuleDto()
+      .setProfileUuid(rulesProfileDto.getUuid())
+      .setRuleUuid(ruleDto.getUuid())
+      .setSeverity(activeRuleSeverity.ordinal());
+    dbClient.activeRuleDao().insert(db.getSession(), activeRuleDto);
+
+    db.commit();
+    return ruleDto;
+  }
 
   private void executeWithPluginRules(RulesDefinition... defs) {
     ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
@@ -1197,7 +1328,7 @@ public class RulesRegistrantIT {
     reset(webServerRuleFinder);
 
     RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, system, webServerRuleFinder, metadataIndex,
-      rulesKeyVerifier, startupRuleUpdater, newRuleCreator, qualityProfileChangesUpdater, sonarQubeVersion, detectPluginChange);
+      rulesKeyVerifier, startupRuleUpdater, newRuleCreator, qualityProfileChangesUpdater, sonarQubeVersion, detectPluginChange, activeRulesImpactInitializer);
     task.start();
     // Execute a commit to refresh session state as the task is using its own session
     db.getSession().commit();
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/ActiveRulesImpactInitializer.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/ActiveRulesImpactInitializer.java
new file mode 100644 (file)
index 0000000..bcce355
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.server.rule.registration;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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.rule.RulesDefinition;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.issue.ImpactDto;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.SeverityUtil;
+import org.sonar.server.property.InternalProperties;
+import org.sonar.server.qualityprofile.QProfileImpactSeverityMapper;
+
+import static java.util.stream.Collectors.toUnmodifiableMap;
+
+/**
+ * Creates the initial impacts on active_rules, based on the rules. This class is necessary only up to 10 LTA version and can be removed
+ * later
+ */
+public class ActiveRulesImpactInitializer {
+  private static final String ACTIVE_RULE_IMPACT_INITIAL_POPULATION_DONE = "activeRules.impacts.populated";
+
+  private final InternalProperties internalProperties;
+  private final DbClient dbClient;
+
+  ActiveRulesImpactInitializer(InternalProperties internalProperties, DbClient dbClient) {
+    this.internalProperties = internalProperties;
+    this.dbClient = dbClient;
+  }
+
+  public void createImpactsOnActiveRules(RulesRegistrationContext context, RulesDefinition.Repository repoDef, DbSession dbSession) {
+
+    if (Boolean.parseBoolean(internalProperties.read(ACTIVE_RULE_IMPACT_INITIAL_POPULATION_DONE).orElse("false"))) {
+      return;
+    }
+
+    Map<String, RuleDto> rules = new HashMap<>(repoDef.rules().size());
+
+    for (RulesDefinition.Rule ruleDef : repoDef.rules()) {
+      context.getDbRuleFor(ruleDef).ifPresent(ruleDto -> rules.put(ruleDto.getUuid(), ruleDto));
+    }
+
+    context.getAllModified().forEach(r -> rules.put(r.getUuid(), r));
+
+    List<ActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByRepository(dbSession, repoDef.key(), repoDef.language());
+
+    for (ActiveRuleDto activeRuleDto : activeRules) {
+      RuleDto rule = rules.get(activeRuleDto.getRuleUuid());
+      if (rule == null || rule.getEnumType() == RuleType.SECURITY_HOTSPOT) {
+        continue;
+      }
+
+      Map<SoftwareQuality, Severity> impacts = toImpactMap(rule.getDefaultImpacts());
+
+      if (!activeRuleDto.getSeverity().equals(rule.getSeverity())) {
+        impacts = QProfileImpactSeverityMapper.mapImpactSeverities(SeverityUtil.getSeverityFromOrdinal(activeRuleDto.getSeverity()), impacts, rule.getEnumType());
+      }
+      activeRuleDto.setImpacts(impacts);
+      dbClient.activeRuleDao().update(dbSession, activeRuleDto);
+    }
+
+  }
+
+  private static Map<SoftwareQuality, Severity> toImpactMap(Collection<ImpactDto> impacts) {
+    return impacts.stream()
+      .collect(toUnmodifiableMap(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity));
+  }
+
+  public void markInitialPopulationDone() {
+    internalProperties.write(ACTIVE_RULE_IMPACT_INITIAL_POPULATION_DONE, "true");
+  }
+}
index 9a022988bab48497239689fd49d7722da051fb94..bafd97fda7712189a260a9a0c9833d954bb463ce 100644 (file)
@@ -83,11 +83,13 @@ public class RulesRegistrant implements Startable {
   private final QualityProfileChangesUpdater qualityProfileChangesUpdater;
   private final SonarQubeVersion sonarQubeVersion;
   private final DetectPluginChange detectPluginChange;
+  private final ActiveRulesImpactInitializer activeRulesImpactInitializer;
 
   public RulesRegistrant(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer,
     ActiveRuleIndexer activeRuleIndexer, System2 system2, WebServerRuleFinder webServerRuleFinder,
     MetadataIndex metadataIndex, RulesKeyVerifier rulesKeyVerifier, StartupRuleUpdater startupRuleUpdater,
-    NewRuleCreator newRuleCreator, QualityProfileChangesUpdater qualityProfileChangesUpdater, SonarQubeVersion sonarQubeVersion, DetectPluginChange detectPluginChange) {
+    NewRuleCreator newRuleCreator, QualityProfileChangesUpdater qualityProfileChangesUpdater, SonarQubeVersion sonarQubeVersion,
+    DetectPluginChange detectPluginChange, ActiveRulesImpactInitializer activeRulesImpactInitializer) {
     this.defLoader = defLoader;
     this.qProfileRules = qProfileRules;
     this.dbClient = dbClient;
@@ -102,6 +104,7 @@ public class RulesRegistrant implements Startable {
     this.qualityProfileChangesUpdater = qualityProfileChangesUpdater;
     this.sonarQubeVersion = sonarQubeVersion;
     this.detectPluginChange = detectPluginChange;
+    this.activeRulesImpactInitializer = activeRulesImpactInitializer;
   }
 
   @Override
@@ -125,12 +128,15 @@ public class RulesRegistrant implements Startable {
           throw new IllegalStateException("Language is mandatory for repository " + repoDef.key());
         }
         Set<PluginRuleUpdate> pluginRuleUpdates = registerRules(rulesRegistrationContext, repoDef.rules(), dbSession);
+
         if (!repoDef.isExternal()) {
           // External rules are not part of quality profiles
+          activeRulesImpactInitializer.createImpactsOnActiveRules(rulesRegistrationContext, repoDef, dbSession);
           qualityProfileChangesUpdater.createQprofileChangesForRuleUpdates(dbSession, pluginRuleUpdates);
         }
         dbSession.commit();
       }
+      activeRulesImpactInitializer.markInitialPopulationDone();
       processRemainingDbRules(rulesRegistrationContext, dbSession);
       List<ActiveRuleChange> changes = anyPluginChanged ? removeActiveRulesOnStillExistingRepositories(dbSession, rulesRegistrationContext, rulesRepositories) : List.of();
       dbSession.commit();
index c7272a49d908b066b5734ad5e0e4206af3c5a568..e331d7d6551e48b81fb6dbd55b271bb10641d63d 100644 (file)
@@ -46,6 +46,7 @@ import org.sonar.server.rule.registration.QualityProfileChangesUpdater;
 import org.sonar.server.rule.registration.RulesKeyVerifier;
 import org.sonar.server.rule.registration.RulesRegistrant;
 import org.sonar.server.rule.registration.StartupRuleUpdater;
+import org.sonar.server.rule.registration.ActiveRulesImpactInitializer;
 import org.sonar.server.startup.RegisterMetrics;
 import org.sonar.server.startup.RegisterPermissionTemplates;
 import org.sonar.server.startup.RegisterPlugins;
@@ -73,6 +74,7 @@ public class PlatformLevelStartup extends PlatformLevel {
       LegacyHotspotRuleDescriptionSectionsGenerator.class,
       LegacyIssueRuleDescriptionSectionsGenerator.class,
       RulesRegistrant.class,
+      ActiveRulesImpactInitializer.class,
       NewRuleCreator.class,
       RulesKeyVerifier.class,
       StartupRuleUpdater.class,