aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com>2023-10-05 17:06:41 +0200
committersonartech <sonartech@sonarsource.com>2023-10-10 20:02:44 +0000
commit9ff811fd2d3421731dfb4097e8748cee1553d2c5 (patch)
tree45d4866bd21131d61def4f67536433aaade279c2
parent047d3846d5e415786323625cd5101a9d54b04725 (diff)
downloadsonarqube-9ff811fd2d3421731dfb4097e8748cee1553d2c5.tar.gz
sonarqube-9ff811fd2d3421731dfb4097e8748cee1553d2c5.zip
SONAR-20548 added event for changing the CCT data for rules
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleChangeDaoIT.java76
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileChangeDto.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeDao.java38
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeDto.java79
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeMapper.java30
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleChangeMapper.xml39
-rw-r--r--server/sonar-db-dao/src/schema/schema-sq.ddl8
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTable.java8
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTableTest.java8
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/rule/PluginRuleUpdate.java85
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/rule/PluginRuleUpdateTest.java48
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/QProfileRules.java2
-rw-r--r--server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java (renamed from server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/RulesRegistrantIT.java)16
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java122
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java27
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java88
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java155
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/StartupRuleUpdaterTest.java126
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java3
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java2
23 files changed, 933 insertions, 45 deletions
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleChangeDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleChangeDaoIT.java
new file mode 100644
index 00000000000..0a3548707c7
--- /dev/null
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleChangeDaoIT.java
@@ -0,0 +1,76 @@
+/*
+ * 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.rule;
+
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+import org.sonarqube.ws.Common;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RuleChangeDaoIT {
+
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+
+ private final RuleChangeDao underTest = db.getDbClient().ruleChangeDao();
+
+ @Test
+ public void insert_shouldInsertRuleChangeWithNullableImpacts() {
+ RuleChangeDto ruleChangeDto = new RuleChangeDto();
+ ruleChangeDto.setNewCleanCodeAttribute(CleanCodeAttribute.CLEAR);
+ ruleChangeDto.setOldCleanCodeAttribute(CleanCodeAttribute.CONVENTIONAL);
+ ruleChangeDto.setRuleUuid("ruleUuid");
+ ruleChangeDto.setUuid("uuid");
+
+ RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto();
+ ruleImpactChangeDto.setNewSoftwareQuality(Common.SoftwareQuality.RELIABILITY.name());
+ ruleImpactChangeDto.setOldSoftwareQuality(Common.SoftwareQuality.RELIABILITY.name());
+ ruleImpactChangeDto.setNewSeverity(Severity.LOW.name());
+ ruleImpactChangeDto.setOldSeverity(Severity.HIGH.name());
+
+ RuleImpactChangeDto ruleImpactChangeDto2 = new RuleImpactChangeDto();
+ ruleImpactChangeDto2.setNewSoftwareQuality(Common.SoftwareQuality.SECURITY.name());
+ ruleImpactChangeDto2.setNewSeverity(Severity.MEDIUM.name());
+
+ RuleImpactChangeDto ruleImpactChangeDto3 = new RuleImpactChangeDto();
+ ruleImpactChangeDto2.setOldSoftwareQuality(Common.SoftwareQuality.MAINTAINABILITY.name());
+ ruleImpactChangeDto2.setOldSeverity(Severity.MEDIUM.name());
+
+ Set<RuleImpactChangeDto> impactChanges = Set.of(ruleImpactChangeDto, ruleImpactChangeDto2, ruleImpactChangeDto3);
+ impactChanges.forEach(i -> i.setRuleChangeUuid(ruleChangeDto.getUuid()));
+
+ ruleChangeDto.setRuleImpactChangeDtos(impactChanges);
+ DbSession session = db.getSession();
+
+ underTest.insert(session, ruleChangeDto);
+ session.commit();
+
+ assertThat(db.select("select * from rule_impact_changes")).hasSize(3);
+ assertThat(db.select("select * from rule_changes")).hasSize(1);
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
index e67eee6703d..cd454bd0d64 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
@@ -82,6 +82,7 @@ import org.sonar.db.qualityprofile.QualityProfileExportDao;
import org.sonar.db.report.RegulatoryReportDao;
import org.sonar.db.report.ReportScheduleDao;
import org.sonar.db.report.ReportSubscriptionDao;
+import org.sonar.db.rule.RuleChangeDao;
import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleRepositoryDao;
import org.sonar.db.scannercache.ScannerAnalysisCacheDao;
@@ -172,6 +173,7 @@ public class DaoModule extends Module {
ReportScheduleDao.class,
RoleDao.class,
RuleDao.class,
+ RuleChangeDao.class,
RuleRepositoryDao.class,
SamlMessageIdDao.class,
ScannerAnalysisCacheDao.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
index 9949da0a223..f4f30d4e406 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
@@ -82,6 +82,7 @@ import org.sonar.db.qualityprofile.QualityProfileExportDao;
import org.sonar.db.report.RegulatoryReportDao;
import org.sonar.db.report.ReportScheduleDao;
import org.sonar.db.report.ReportSubscriptionDao;
+import org.sonar.db.rule.RuleChangeDao;
import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleRepositoryDao;
import org.sonar.db.scannercache.ScannerAnalysisCacheDao;
@@ -189,6 +190,7 @@ public class DbClient {
private final ReportSubscriptionDao reportSubscriptionDao;
private final GithubOrganizationGroupDao githubOrganizationGroupDao;
private final GithubPermissionsMappingDao githubPermissionsMappingDao;
+ private final RuleChangeDao ruleChangeDao;
public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) {
this.database = database;
@@ -279,6 +281,7 @@ public class DbClient {
reportScheduleDao = getDao(map, ReportScheduleDao.class);
reportSubscriptionDao = getDao(map, ReportSubscriptionDao.class);
anticipatedTransitionDao = getDao(map, AnticipatedTransitionDao.class);
+ ruleChangeDao = getDao(map, RuleChangeDao.class);
}
public DbSession openSession(boolean batch) {
@@ -618,5 +621,9 @@ public class DbClient {
public AnticipatedTransitionDao anticipatedTransitionDao() {
return anticipatedTransitionDao;
}
+
+ public RuleChangeDao ruleChangeDao() {
+ return ruleChangeDao;
+ }
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
index 76f0e4a5faf..0ffad887f73 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
@@ -146,6 +146,7 @@ import org.sonar.db.qualityprofile.QualityProfileMapper;
import org.sonar.db.report.RegulatoryReportMapper;
import org.sonar.db.report.ReportScheduleMapper;
import org.sonar.db.report.ReportSubscriptionMapper;
+import org.sonar.db.rule.RuleChangeMapper;
import org.sonar.db.rule.RuleMapper;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.rule.RuleRepositoryMapper;
@@ -331,6 +332,7 @@ public class MyBatis {
ReportSubscriptionMapper.class,
RoleMapper.class,
RuleMapper.class,
+ RuleChangeMapper.class,
RuleRepositoryMapper.class,
SamlMessageIdMapper.class,
ScannerAnalysisCacheMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileChangeDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileChangeDto.java
index 9e2c7b2bce9..2bf6361d8dc 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileChangeDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileChangeDto.java
@@ -45,6 +45,7 @@ public class QProfileChangeDto {
private Set<RuleImpactChangeDto> ruleImpactChangeDtos;
private long createdAt;
+ private String ruleChangeUuid;
public String getUuid() {
return uuid;
@@ -147,5 +148,11 @@ public class QProfileChangeDto {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
+ public void setRuleChangeUuid(String ruleChangeUuid) {
+ this.ruleChangeUuid = ruleChangeUuid;
+ }
+ public String getRuleChangeUuid() {
+ return ruleChangeUuid;
+ }
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeDao.java
new file mode 100644
index 00000000000..0b7ee3133d2
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeDao.java
@@ -0,0 +1,38 @@
+/*
+ * 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.rule;
+
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+
+public class RuleChangeDao implements Dao {
+
+ /**
+ * Inserts a rule change with its impacts.
+ * The method doesn't commit its transaction.
+ */
+ public void insert(DbSession session, RuleChangeDto ruleChangeDto) {
+ for (RuleImpactChangeDto ruleImpactChangeDto : ruleChangeDto.getRuleImpactChangeDtos()) {
+ session.getMapper(RuleChangeMapper.class).insertRuleImpactChange(ruleImpactChangeDto);
+ }
+ session.getMapper(RuleChangeMapper.class).insertRuleChange(ruleChangeDto);
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeDto.java
new file mode 100644
index 00000000000..776817fe063
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeDto.java
@@ -0,0 +1,79 @@
+/*
+ * 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.rule;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+
+public class RuleChangeDto {
+
+ private String uuid;
+ private CleanCodeAttribute oldCleanCodeAttribute;
+ private CleanCodeAttribute newCleanCodeAttribute;
+ private Set<RuleImpactChangeDto> ruleImpactChangeDtos = new HashSet<>();
+ private String ruleUuid;
+
+ public CleanCodeAttribute getOldCleanCodeAttribute() {
+ return oldCleanCodeAttribute;
+ }
+
+ public void setOldCleanCodeAttribute(CleanCodeAttribute oldCleanCodeAttribute) {
+ this.oldCleanCodeAttribute = oldCleanCodeAttribute;
+ }
+
+ public CleanCodeAttribute getNewCleanCodeAttribute() {
+ return newCleanCodeAttribute;
+ }
+
+ public void setNewCleanCodeAttribute(CleanCodeAttribute newCleanCodeAttribute) {
+ this.newCleanCodeAttribute = newCleanCodeAttribute;
+ }
+
+ public Set<RuleImpactChangeDto> getRuleImpactChangeDtos() {
+ return ruleImpactChangeDtos;
+ }
+
+ public void setRuleImpactChangeDtos(Set<RuleImpactChangeDto> ruleImpactChangeDtos) {
+ this.ruleImpactChangeDtos = ruleImpactChangeDtos;
+ }
+
+ public String getRuleUuid() {
+ return ruleUuid;
+ }
+
+ public void setRuleUuid(String ruleUuid) {
+ this.ruleUuid = ruleUuid;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public void addRuleImpactChangeDto(RuleImpactChangeDto ruleImpactChangeDto) {
+ this.ruleImpactChangeDtos.add(ruleImpactChangeDto);
+ }
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeMapper.java
new file mode 100644
index 00000000000..7d344f68091
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleChangeMapper.java
@@ -0,0 +1,30 @@
+/*
+ * 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.rule;
+
+import org.apache.ibatis.annotations.Param;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+
+public interface RuleChangeMapper {
+
+ void insertRuleChange(@Param("dto") RuleChangeDto dto);
+
+ void insertRuleImpactChange(@Param("dto") RuleImpactChangeDto dto);
+}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleChangeMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleChangeMapper.xml
new file mode 100644
index 00000000000..430d6187ddd
--- /dev/null
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleChangeMapper.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.rule.RuleChangeMapper">
+
+ <insert id="insertRuleChange" parameterType="Map" useGeneratedKeys="false">
+ insert into rule_changes (
+ uuid,
+ new_clean_code_attribute,
+ old_clean_code_attribute,
+ rule_uuid
+ )
+ values (
+ #{dto.uuid,jdbcType=VARCHAR},
+ #{dto.newCleanCodeAttribute,jdbcType=VARCHAR},
+ #{dto.oldCleanCodeAttribute,jdbcType=VARCHAR},
+ #{dto.ruleUuid,jdbcType=VARCHAR}
+ )
+ </insert>
+
+ <insert id="insertRuleImpactChange" parameterType="Map" useGeneratedKeys="false">
+ insert into rule_impact_changes (
+ new_software_quality,
+ old_software_quality,
+ new_severity,
+ old_severity,
+ rule_change_uuid
+ )
+ values (
+ #{dto.newSoftwareQuality,jdbcType=VARCHAR},
+ #{dto.oldSoftwareQuality,jdbcType=VARCHAR},
+ #{dto.newSeverity,jdbcType=VARCHAR},
+ #{dto.oldSeverity,jdbcType=VARCHAR},
+ #{dto.ruleChangeUuid,jdbcType=VARCHAR}
+ )
+ </insert>
+
+</mapper>
+
diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl
index f8fb47e1b5a..bc77b77d309 100644
--- a/server/sonar-db-dao/src/schema/schema-sq.ddl
+++ b/server/sonar-db-dao/src/schema/schema-sq.ddl
@@ -894,10 +894,10 @@ ALTER TABLE "RULE_DESC_SECTIONS" ADD CONSTRAINT "PK_RULE_DESC_SECTIONS" PRIMARY
CREATE UNIQUE NULLS DISTINCT INDEX "UNIQ_RULE_DESC_SECTIONS" ON "RULE_DESC_SECTIONS"("RULE_UUID" NULLS FIRST, "KEE" NULLS FIRST, "CONTEXT_KEY" NULLS FIRST);
CREATE TABLE "RULE_IMPACT_CHANGES"(
- "NEW_SOFTWARE_QUALITY" CHARACTER VARYING(40) NOT NULL,
- "OLD_SOFTWARE_QUALITY" CHARACTER VARYING(40) NOT NULL,
- "NEW_SEVERITY" CHARACTER VARYING(40) NOT NULL,
- "OLD_SEVERITY" CHARACTER VARYING(40) NOT NULL,
+ "NEW_SOFTWARE_QUALITY" CHARACTER VARYING(40),
+ "OLD_SOFTWARE_QUALITY" CHARACTER VARYING(40),
+ "NEW_SEVERITY" CHARACTER VARYING(40),
+ "OLD_SEVERITY" CHARACTER VARYING(40),
"RULE_CHANGE_UUID" CHARACTER VARYING(40) NOT NULL
);
CREATE INDEX "RULE_IMPACT_CHANGES_R_C_UUID" ON "RULE_IMPACT_CHANGES"("RULE_CHANGE_UUID" NULLS FIRST);
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTable.java
index 83743116ec6..6bd72825059 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTable.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTable.java
@@ -38,10 +38,10 @@ public class CreateRuleImpactChangesTable extends CreateTableChange {
@Override
public void execute(DdlChange.Context context, String tableName) throws SQLException {
context.execute(new CreateTableBuilder(getDialect(), tableName)
- .addColumn(newVarcharColumnDefBuilder().setColumnName("new_software_quality").setIsNullable(false).setLimit(40).build())
- .addColumn(newVarcharColumnDefBuilder().setColumnName("old_software_quality").setIsNullable(false).setLimit(40).build())
- .addColumn(newVarcharColumnDefBuilder().setColumnName("new_severity").setIsNullable(false).setLimit(40).build())
- .addColumn(newVarcharColumnDefBuilder().setColumnName("old_severity").setIsNullable(false).setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("new_software_quality").setIsNullable(true).setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("old_software_quality").setIsNullable(true).setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("new_severity").setIsNullable(true).setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("old_severity").setIsNullable(true).setLimit(40).build())
.addColumn(newVarcharColumnDefBuilder().setColumnName("rule_change_uuid").setIsNullable(false).setLimit(40).build())
.build());
}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTableTest.java
index d9c382bfd43..fe39dfc3bd3 100644
--- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTableTest.java
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v103/CreateRuleImpactChangesTableTest.java
@@ -45,10 +45,10 @@ public class CreateRuleImpactChangesTableTest {
underTest.execute();
db.assertTableExists(TABLE_NAME);
- db.assertColumnDefinition(TABLE_NAME, "new_software_quality", Types.VARCHAR, 40, false);
- db.assertColumnDefinition(TABLE_NAME, "old_software_quality", Types.VARCHAR, 40, false);
- db.assertColumnDefinition(TABLE_NAME, "new_severity", Types.VARCHAR, 40, false);
- db.assertColumnDefinition(TABLE_NAME, "old_severity", Types.VARCHAR, 40, false);
+ db.assertColumnDefinition(TABLE_NAME, "new_software_quality", Types.VARCHAR, 40, true);
+ db.assertColumnDefinition(TABLE_NAME, "old_software_quality", Types.VARCHAR, 40, true);
+ db.assertColumnDefinition(TABLE_NAME, "new_severity", Types.VARCHAR, 40, true);
+ db.assertColumnDefinition(TABLE_NAME, "old_severity", Types.VARCHAR, 40, true);
db.assertColumnDefinition(TABLE_NAME, "rule_change_uuid", Types.VARCHAR, UUID_SIZE, false);
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/PluginRuleUpdate.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/PluginRuleUpdate.java
new file mode 100644
index 00000000000..762ac0bed57
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/PluginRuleUpdate.java
@@ -0,0 +1,85 @@
+/*
+ * 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.server.rule;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.CleanCodeAttribute;
+
+/**
+ * Represents a single update of a single rule done by new version of plugins at startup
+ */
+public class PluginRuleUpdate {
+
+ private String ruleUuid;
+
+ private CleanCodeAttribute newCleanCodeAttribute;
+ private CleanCodeAttribute oldCleanCodeAttribute;
+ private final Map<SoftwareQuality, Severity> newImpacts = new EnumMap<>(SoftwareQuality.class);
+ private final Map<SoftwareQuality, Severity> oldImpacts = new EnumMap<>(SoftwareQuality.class);
+
+ public String getRuleUuid() {
+ return ruleUuid;
+ }
+
+ public void setRuleUuid(String ruleUuid) {
+ this.ruleUuid = ruleUuid;
+ }
+
+ public void addOldImpact(SoftwareQuality softwareQuality, Severity severity) {
+ oldImpacts.put(softwareQuality, severity);
+ }
+
+ public void addNewImpact(SoftwareQuality softwareQuality, Severity severity) {
+ newImpacts.put(softwareQuality, severity);
+ }
+
+ public Map<SoftwareQuality, Severity> getNewImpacts() {
+ return newImpacts;
+ }
+
+ public Map<SoftwareQuality, Severity> getOldImpacts() {
+ return oldImpacts;
+ }
+
+ public List<SoftwareQuality> getMatchingSoftwareQualities() {
+ return newImpacts.keySet().stream().filter(oldImpacts::containsKey).toList();
+ }
+
+ public CleanCodeAttribute getNewCleanCodeAttribute() {
+ return newCleanCodeAttribute;
+ }
+
+ public void setNewCleanCodeAttribute(@Nullable CleanCodeAttribute newCleanCodeAttribute) {
+ this.newCleanCodeAttribute = newCleanCodeAttribute;
+ }
+
+ public CleanCodeAttribute getOldCleanCodeAttribute() {
+ return oldCleanCodeAttribute;
+ }
+
+ public void setOldCleanCodeAttribute(@Nullable CleanCodeAttribute oldCleanCodeAttribute) {
+ this.oldCleanCodeAttribute = oldCleanCodeAttribute;
+ }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/PluginRuleUpdateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/PluginRuleUpdateTest.java
new file mode 100644
index 00000000000..c44bcc2e07c
--- /dev/null
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/rule/PluginRuleUpdateTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.rule;
+
+import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PluginRuleUpdateTest {
+
+ @Test
+ public void addOldImpact_whenOldImpactAdded_shouldContainOneImpact() {
+ PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate();
+
+ pluginRuleUpdate.addOldImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
+
+ assertThat(pluginRuleUpdate.getOldImpacts()).hasSize(1);
+ }
+
+ @Test
+ public void addNewImpact_whenNewImpactAdded_shouldContainOneImpact() {
+ PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate();
+
+ pluginRuleUpdate.addNewImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
+
+ assertThat(pluginRuleUpdate.getNewImpacts()).hasSize(1);
+ }
+
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/QProfileRules.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/QProfileRules.java
index 0c1c99961fe..eced908e573 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/QProfileRules.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/QProfileRules.java
@@ -29,7 +29,7 @@ import org.sonar.db.rule.RuleDto;
import org.sonar.server.rule.index.RuleQuery;
/**
- * Operations related to activation and deactivation of rules on user profiles.
+ * Operations related to activation and deactivation of rules on Quality profiles.
*/
@ServerSide
public interface QProfileRules {
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/RulesRegistrantIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java
index 28b43b9d6a4..b1b9c715829 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/RulesRegistrantIT.java
+++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java
@@ -50,8 +50,8 @@ 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.utils.DateUtils;
import org.sonar.api.testfixtures.log.LogTester;
+import org.sonar.api.utils.DateUtils;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbClient;
@@ -105,7 +105,6 @@ import static org.mockito.Mockito.when;
import static org.sonar.api.rule.RuleStatus.READY;
import static org.sonar.api.rule.RuleStatus.REMOVED;
import static org.sonar.api.rule.Severity.BLOCKER;
-import static org.sonar.api.rule.Severity.CRITICAL;
import static org.sonar.api.rule.Severity.INFO;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
@@ -145,21 +144,22 @@ public class RulesRegistrantIT {
@org.junit.Rule
public LogTester logTester = new LogTester();
- private final QProfileRules qProfileRules = mock(QProfileRules.class);
- private final WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class);
+ private final QProfileRules qProfileRules = mock();
+ private final WebServerRuleFinder webServerRuleFinder = mock();
private final DbClient dbClient = db.getDbClient();
- private final MetadataIndex metadataIndex = mock(MetadataIndex.class);
+ private final MetadataIndex metadataIndex = mock();
private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
private RuleIndexer ruleIndexer;
private ActiveRuleIndexer activeRuleIndexer;
private RuleIndex ruleIndex;
- private final RuleDescriptionSectionsGenerator ruleDescriptionSectionsGenerator = mock(RuleDescriptionSectionsGenerator.class);
- private final RuleDescriptionSectionsGeneratorResolver resolver = mock(RuleDescriptionSectionsGeneratorResolver.class);
+ private final RuleDescriptionSectionsGenerator ruleDescriptionSectionsGenerator = mock();
+ private final RuleDescriptionSectionsGeneratorResolver resolver = mock();
private final RulesKeyVerifier rulesKeyVerifier = new RulesKeyVerifier();
private final StartupRuleUpdater startupRuleUpdater = new StartupRuleUpdater(dbClient, system, uuidFactory, resolver);
private final NewRuleCreator newRuleCreator = new NewRuleCreator(dbClient, resolver, uuidFactory, system);
+ private final QualityProfileChangesUpdater qualityProfileChangesUpdater = mock();
@Before
public void before() {
@@ -1162,7 +1162,7 @@ public class RulesRegistrantIT {
reset(webServerRuleFinder);
RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, metadataIndex,
- rulesKeyVerifier, startupRuleUpdater, newRuleCreator);
+ rulesKeyVerifier, startupRuleUpdater, newRuleCreator, qualityProfileChangesUpdater);
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/QualityProfileChangesUpdater.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java
new file mode 100644
index 00000000000..e58b4b8ce57
--- /dev/null
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java
@@ -0,0 +1,122 @@
+/*
+ * 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.server.rule.registration;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.QProfileChangeDto;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+import org.sonar.db.rule.RuleChangeDto;
+import org.sonar.server.rule.PluginRuleUpdate;
+
+public class QualityProfileChangesUpdater {
+
+ private final DbClient dbClient;
+ private final UuidFactory uuidFactory;
+
+ public QualityProfileChangesUpdater(DbClient dbClient, UuidFactory uuidFactory) {
+ this.dbClient = dbClient;
+ this.uuidFactory = uuidFactory;
+ }
+
+ public void updateWithoutCommit(DbSession dbSession, Set<PluginRuleUpdate> pluginRuleUpdates) {
+ for (PluginRuleUpdate pluginRuleUpdate : pluginRuleUpdates) {
+ String ruleChangeUuid = uuidFactory.create();
+ RuleChangeDto ruleChangeDto = createRuleChange(ruleChangeUuid, pluginRuleUpdate);
+
+ createRuleImpactChanges(ruleChangeUuid, pluginRuleUpdate, ruleChangeDto);
+ insertRuleChange(dbSession, ruleChangeDto);
+
+ for (String qualityProfileUuid : findQualityProfilesForRule(dbSession, pluginRuleUpdate.getRuleUuid())) {
+ QProfileChangeDto qProfileChangeDto = new QProfileChangeDto();
+ qProfileChangeDto.setUuid(uuidFactory.create());
+ qProfileChangeDto.setChangeType("UPDATED");
+ qProfileChangeDto.setRuleChangeUuid(ruleChangeUuid);
+ qProfileChangeDto.setRulesProfileUuid(qualityProfileUuid);
+ dbClient.qProfileChangeDao().insert(dbSession, qProfileChangeDto);
+ }
+
+ }
+ }
+
+ private static RuleChangeDto createRuleChange(String ruleChangeUuid, PluginRuleUpdate pluginRuleUpdate) {
+ RuleChangeDto ruleChangeDto = new RuleChangeDto();
+ ruleChangeDto.setUuid(ruleChangeUuid);
+ ruleChangeDto.setRuleUuid(pluginRuleUpdate.getRuleUuid());
+ ruleChangeDto.setOldCleanCodeAttribute(pluginRuleUpdate.getOldCleanCodeAttribute());
+ ruleChangeDto.setNewCleanCodeAttribute(pluginRuleUpdate.getNewCleanCodeAttribute());
+ return ruleChangeDto;
+ }
+
+ private Set<String> findQualityProfilesForRule(DbSession dbSession, String ruleUuid) {
+ return dbClient.activeRuleDao().selectByRuleUuid(dbSession, ruleUuid)
+ .stream()
+ .map(ActiveRuleDto::getProfileUuid)
+ .collect(Collectors.toSet());
+ }
+
+ private void insertRuleChange(DbSession dbSession, RuleChangeDto ruleChangeDto) {
+ dbClient.ruleChangeDao().insert(dbSession, ruleChangeDto);
+ }
+
+ private static void createRuleImpactChanges(String ruleChangeUuid, PluginRuleUpdate pluginRuleUpdate, RuleChangeDto ruleChangeDto) {
+ List<SoftwareQuality> matchingSoftwareQualities = pluginRuleUpdate.getMatchingSoftwareQualities();
+ for (SoftwareQuality softwareQuality : matchingSoftwareQualities) {
+ RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto();
+ ruleImpactChangeDto.setRuleChangeUuid(ruleChangeUuid);
+ ruleImpactChangeDto.setOldSeverity(pluginRuleUpdate.getOldImpacts().get(softwareQuality).name());
+ ruleImpactChangeDto.setOldSoftwareQuality(softwareQuality.name());
+ ruleImpactChangeDto.setNewSeverity(pluginRuleUpdate.getNewImpacts().get(softwareQuality).name());
+ ruleImpactChangeDto.setNewSoftwareQuality(softwareQuality.name());
+ ruleChangeDto.addRuleImpactChangeDto(ruleImpactChangeDto);
+ }
+
+ List<SoftwareQuality> oldSoftwareQualities = pluginRuleUpdate.getOldImpacts().keySet()
+ .stream()
+ .filter(softwareQuality -> !matchingSoftwareQualities.contains(softwareQuality)).toList();
+
+ List<SoftwareQuality> newSoftwareQualities = pluginRuleUpdate.getNewImpacts().keySet()
+ .stream()
+ .filter(softwareQuality -> !matchingSoftwareQualities.contains(softwareQuality)).toList();
+
+ int size = Math.max(oldSoftwareQualities.size(), newSoftwareQualities.size());
+ for(int i = 0; i < size; i++) {
+ RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto();
+ ruleImpactChangeDto.setRuleChangeUuid(ruleChangeUuid);
+ if(i < oldSoftwareQualities.size()) {
+ ruleImpactChangeDto.setOldSeverity(pluginRuleUpdate.getOldImpacts().get(oldSoftwareQualities.get(i)).name());
+ ruleImpactChangeDto.setOldSoftwareQuality(oldSoftwareQualities.get(i).name());
+ }
+ if(i < newSoftwareQualities.size()) {
+ ruleImpactChangeDto.setNewSeverity(pluginRuleUpdate.getNewImpacts().get(newSoftwareQualities.get(i)).name());
+ ruleImpactChangeDto.setNewSoftwareQuality(newSoftwareQualities.get(i).name());
+ }
+ ruleChangeDto.addRuleImpactChangeDto(ruleImpactChangeDto);
+ }
+
+
+ }
+}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java
index 311fa04f82a..26575827823 100644
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java
@@ -21,6 +21,7 @@ package org.sonar.server.rule.registration;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -45,6 +46,7 @@ import org.sonar.db.rule.RuleRepositoryDto;
import org.sonar.server.es.metadata.MetadataIndex;
import org.sonar.server.qualityprofile.ActiveRuleChange;
import org.sonar.server.qualityprofile.QProfileRules;
+import org.sonar.server.rule.PluginRuleUpdate;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.RuleDefinitionsLoader;
import org.sonar.server.rule.WebServerRuleFinder;
@@ -74,11 +76,12 @@ public class RulesRegistrant implements Startable {
private final RulesKeyVerifier rulesKeyVerifier;
private final StartupRuleUpdater startupRuleUpdater;
private final NewRuleCreator newRuleCreator;
+ private final QualityProfileChangesUpdater qualityProfileChangesUpdater;
public RulesRegistrant(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer,
ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, WebServerRuleFinder webServerRuleFinder,
MetadataIndex metadataIndex, RulesKeyVerifier rulesKeyVerifier, StartupRuleUpdater startupRuleUpdater,
- NewRuleCreator newRuleCreator) {
+ NewRuleCreator newRuleCreator, QualityProfileChangesUpdater qualityProfileChangesUpdater) {
this.defLoader = defLoader;
this.qProfileRules = qProfileRules;
this.dbClient = dbClient;
@@ -91,6 +94,7 @@ public class RulesRegistrant implements Startable {
this.rulesKeyVerifier = rulesKeyVerifier;
this.startupRuleUpdater = startupRuleUpdater;
this.newRuleCreator = newRuleCreator;
+ this.qualityProfileChangesUpdater = qualityProfileChangesUpdater;
}
@Override
@@ -103,7 +107,8 @@ public class RulesRegistrant implements Startable {
for (RulesDefinition.ExtendedRepository repoDef : repositories) {
if (languages.get(repoDef.language()) != null) {
- registerRules(rulesRegistrationContext, repoDef.rules(), dbSession);
+ Set<PluginRuleUpdate> pluginRuleUpdates = registerRules(rulesRegistrationContext, repoDef.rules(), dbSession);
+ qualityProfileChangesUpdater.updateWithoutCommit(dbSession, pluginRuleUpdates);
dbSession.commit();
}
}
@@ -148,8 +153,9 @@ public class RulesRegistrant implements Startable {
// nothing
}
- private void registerRules(RulesRegistrationContext context, List<RulesDefinition.Rule> ruleDefs, DbSession session) {
+ private Set<PluginRuleUpdate> registerRules(RulesRegistrationContext context, List<RulesDefinition.Rule> ruleDefs, DbSession session) {
Map<RulesDefinition.Rule, RuleDto> dtos = new LinkedHashMap<>(ruleDefs.size());
+ Set<PluginRuleUpdate> pluginRuleUpdates = new HashSet<>();
for (RulesDefinition.Rule ruleDef : ruleDefs) {
RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
@@ -162,8 +168,8 @@ public class RulesRegistrant implements Startable {
ruleDto.setRuleKey(ruleKey);
}
- if (!context.isCreated(ruleDto) && startupRuleUpdater.findChangesAndUpdateRule(ruleDef, ruleDto)) {
- context.updated(ruleDto);
+ if (!context.isCreated(ruleDto)) {
+ processRuleUpdates(context, pluginRuleUpdates, ruleDef, ruleDto);
}
if (context.isUpdated(ruleDto) || context.isRenamed(ruleDto)) {
@@ -177,6 +183,17 @@ public class RulesRegistrant implements Startable {
startupRuleUpdater.mergeParams(context, e.getKey(), e.getValue(), session);
startupRuleUpdater.updateDeprecatedKeys(context, e.getKey(), e.getValue(), session);
}
+ return pluginRuleUpdates;
+ }
+
+ private void processRuleUpdates(RulesRegistrationContext context, Set<PluginRuleUpdate> pluginRuleUpdates, RulesDefinition.Rule ruleDef, RuleDto ruleDto) {
+ StartupRuleUpdater.RuleChange change = startupRuleUpdater.findChangesAndUpdateRule(ruleDef, ruleDto);
+ if (change.hasRuleDefinitionChanged()) {
+ context.updated(ruleDto);
+ if (change.getPluginRuleUpdate() != null) {
+ pluginRuleUpdates.add(change.getPluginRuleUpdate());
+ }
+ }
}
private void processRemainingDbRules(RulesRegistrationContext recorder, DbSession dbSession) {
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java
index 1eb8793b20c..e9eedabd6c4 100644
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java
@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.issue.impact.Severity;
@@ -49,6 +50,7 @@ import org.sonar.db.rule.DeprecatedRuleKeyDto;
import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.rule.PluginRuleUpdate;
import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
import static com.google.common.collect.Sets.difference;
@@ -80,13 +82,15 @@ public class StartupRuleUpdater {
/**
* Returns true in case there was any change detected between rule in the database and rule from the plugin.
*/
- boolean findChangesAndUpdateRule(RulesDefinition.Rule ruleDef, RuleDto ruleDto) {
- boolean ruleMerged = mergeRule(ruleDef, ruleDto);
+ RuleChange findChangesAndUpdateRule(RulesDefinition.Rule ruleDef, RuleDto ruleDto) {
+ RuleChange ruleChange = new RuleChange(ruleDto);
+ boolean ruleMerged = mergeRule(ruleDef, ruleDto, ruleChange);
boolean debtDefinitionsMerged = mergeDebtDefinitions(ruleDef, ruleDto);
boolean tagsMerged = mergeTags(ruleDef, ruleDto);
boolean securityStandardsMerged = mergeSecurityStandards(ruleDef, ruleDto);
boolean educationPrinciplesMerged = mergeEducationPrinciples(ruleDef, ruleDto);
- return ruleMerged || debtDefinitionsMerged || tagsMerged || securityStandardsMerged || educationPrinciplesMerged;
+ ruleChange.ruleDefinitionChanged = ruleMerged || debtDefinitionsMerged || tagsMerged || securityStandardsMerged || educationPrinciplesMerged;
+ return ruleChange;
}
void updateDeprecatedKeys(RulesRegistrationContext context, RulesDefinition.Rule ruleDef, RuleDto rule, DbSession dbSession) {
@@ -112,7 +116,7 @@ public class StartupRuleUpdater {
.setCreatedAt(system2.now())));
}
- private boolean mergeRule(RulesDefinition.Rule def, RuleDto dto) {
+ private boolean mergeRule(RulesDefinition.Rule def, RuleDto dto, RuleChange ruleChange) {
boolean changed = false;
if (!Objects.equals(dto.getName(), def.name())) {
dto.setName(def.name());
@@ -156,8 +160,8 @@ public class StartupRuleUpdater {
dto.setType(type);
changed = true;
}
- changed |= mergeCleanCodeAttribute(def, dto);
- changed |= mergeImpacts(def, dto, uuidFactory);
+ changed |= mergeCleanCodeAttribute(def, dto, ruleChange);
+ changed |= mergeImpacts(def, dto, uuidFactory, ruleChange);
if (dto.isAdHoc()) {
dto.setIsAdHoc(false);
changed = true;
@@ -165,13 +169,14 @@ public class StartupRuleUpdater {
return changed;
}
- private static boolean mergeCleanCodeAttribute(RulesDefinition.Rule def, RuleDto dto) {
+ private static boolean mergeCleanCodeAttribute(RulesDefinition.Rule def, RuleDto dto, RuleChange ruleChange) {
if (dto.getEnumType() == RuleType.SECURITY_HOTSPOT) {
return false;
}
boolean changed = false;
CleanCodeAttribute defCleanCodeAttribute = def.cleanCodeAttribute();
if (!Objects.equals(dto.getCleanCodeAttribute(), defCleanCodeAttribute) && (defCleanCodeAttribute != null)) {
+ ruleChange.addCleanCodeAttributeChange(dto.getCleanCodeAttribute(), defCleanCodeAttribute);
dto.setCleanCodeAttribute(defCleanCodeAttribute);
changed = true;
}
@@ -183,7 +188,7 @@ public class StartupRuleUpdater {
return changed;
}
- boolean mergeImpacts(RulesDefinition.Rule def, RuleDto dto, UuidFactory uuidFactory) {
+ boolean mergeImpacts(RulesDefinition.Rule def, RuleDto dto, UuidFactory uuidFactory, RuleChange ruleChange) {
if (dto.getEnumType() == RuleType.SECURITY_HOTSPOT) {
return false;
}
@@ -200,16 +205,28 @@ public class StartupRuleUpdater {
.stream()
.map(e -> new ImpactDto().setUuid(uuidFactory.create()).setSoftwareQuality(e.getKey()).setSeverity(e.getValue()))
.collect(Collectors.toSet()));
+ ruleChange.addImpactsChange(removeDuplicatedImpacts(impactsFromDb, impactsFromPlugin), removeDuplicatedImpacts(impactsFromPlugin, impactsFromDb));
+
return true;
}
return false;
}
+ /**
+ * Returns a new map that contains only the impacts from the first map that are not present in the map passed as a second argument.
+ */
+ private static Map<SoftwareQuality, Severity> removeDuplicatedImpacts(Map<SoftwareQuality, Severity> impactsA, Map<SoftwareQuality, Severity> impactsB) {
+ return impactsA.entrySet().stream()
+ .filter(entry -> !impactsB.containsKey(entry.getKey()) || !impactsB.get(entry.getKey()).equals(entry.getValue()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+
private static boolean mergeEducationPrinciples(RulesDefinition.Rule ruleDef, RuleDto dto) {
boolean changed = false;
if (dto.getEducationPrinciples().size() != ruleDef.educationPrincipleKeys().size() ||
- !dto.getEducationPrinciples().containsAll(ruleDef.educationPrincipleKeys())) {
+ !dto.getEducationPrinciples().containsAll(ruleDef.educationPrincipleKeys())) {
dto.setEducationPrinciples(ruleDef.educationPrincipleKeys());
changed = true;
}
@@ -223,10 +240,10 @@ public class StartupRuleUpdater {
dto.setSystemTags(emptySet());
changed = true;
} else if (dto.getSystemTags().size() != ruleDef.tags().size() ||
- !dto.getSystemTags().containsAll(ruleDef.tags())) {
- dto.setSystemTags(ruleDef.tags());
- changed = true;
- }
+ !dto.getSystemTags().containsAll(ruleDef.tags())) {
+ dto.setSystemTags(ruleDef.tags());
+ changed = true;
+ }
return changed;
}
@@ -237,10 +254,10 @@ public class StartupRuleUpdater {
dto.setSecurityStandards(emptySet());
changed = true;
} else if (dto.getSecurityStandards().size() != ruleDef.securityStandards().size() ||
- !dto.getSecurityStandards().containsAll(ruleDef.securityStandards())) {
- dto.setSecurityStandards(ruleDef.securityStandards());
- changed = true;
- }
+ !dto.getSecurityStandards().containsAll(ruleDef.securityStandards())) {
+ dto.setSecurityStandards(ruleDef.securityStandards());
+ changed = true;
+ }
return changed;
}
@@ -377,4 +394,41 @@ public class StartupRuleUpdater {
return changed;
}
+ public static class RuleChange {
+ private boolean ruleDefinitionChanged = false;
+ private final String ruleUuid;
+ private PluginRuleUpdate pluginRuleUpdate;
+
+ public RuleChange(RuleDto ruleDto) {
+ this.ruleUuid = ruleDto.getUuid();
+ }
+
+ private void createPluginRuleUpdateIfNeeded() {
+ if (pluginRuleUpdate == null) {
+ pluginRuleUpdate = new PluginRuleUpdate();
+ pluginRuleUpdate.setRuleUuid(ruleUuid);
+ }
+ }
+
+ public void addImpactsChange(Map<SoftwareQuality, Severity> oldImpacts, Map<SoftwareQuality, Severity> newImpacts) {
+ createPluginRuleUpdateIfNeeded();
+ oldImpacts.forEach(pluginRuleUpdate::addOldImpact);
+ newImpacts.forEach(pluginRuleUpdate::addNewImpact);
+ }
+
+ public void addCleanCodeAttributeChange(@Nullable CleanCodeAttribute oldAttribute, @Nullable CleanCodeAttribute newAttribute) {
+ createPluginRuleUpdateIfNeeded();
+ pluginRuleUpdate.setOldCleanCodeAttribute(oldAttribute);
+ pluginRuleUpdate.setNewCleanCodeAttribute(newAttribute);
+ }
+
+ public boolean hasRuleDefinitionChanged() {
+ return ruleDefinitionChanged;
+ }
+
+ @CheckForNull
+ public PluginRuleUpdate getPluginRuleUpdate() {
+ return pluginRuleUpdate;
+ }
+ }
}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java
new file mode 100644
index 00000000000..29d3ab8f08e
--- /dev/null
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.server.rule.registration;
+
+import java.util.List;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.core.util.UuidFactoryImpl;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.ActiveRuleDao;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.QProfileChangeDao;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+import org.sonar.db.rule.RuleChangeDao;
+import org.sonar.db.rule.RuleChangeDto;
+import org.sonar.server.rule.PluginRuleUpdate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+public class QualityProfileChangesUpdaterTest {
+
+ public static final String RULE_UUID = "ruleUuid";
+ private final DbClient dbClient = mock();
+ private final DbSession dbSession = mock();
+ private final RuleChangeDao ruleChangeDao = mock();
+ private final QProfileChangeDao qualityProfileChangeDao = mock();
+ private final ActiveRuleDao activeRuleDao = mock();
+
+ private final QualityProfileChangesUpdater underTest = new QualityProfileChangesUpdater(dbClient, UuidFactoryImpl.INSTANCE);
+
+ @Before
+ public void before() {
+ when(dbClient.ruleChangeDao()).thenReturn(ruleChangeDao);
+ when(dbClient.qProfileChangeDao()).thenReturn(qualityProfileChangeDao);
+ when(dbClient.activeRuleDao()).thenReturn(activeRuleDao);
+ }
+
+ @Test
+ public void updateWithoutCommit_whenNoRuleChanges_thenDontInteractWithDatabase() {
+ underTest.updateWithoutCommit(mock(), Set.of());
+
+ verifyNoInteractions(dbClient);
+ }
+
+ @Test
+ public void updateWithoutCommit_whenOneRuleChangedItsAttribute_thenInsertRuleChangeButNotImpactChange() {
+ PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate();
+ pluginRuleUpdate.setNewCleanCodeAttribute(CleanCodeAttribute.CLEAR);
+ pluginRuleUpdate.setOldCleanCodeAttribute(CleanCodeAttribute.TESTED);
+ pluginRuleUpdate.setRuleUuid(RULE_UUID);
+
+ underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate));
+
+ verify(ruleChangeDao).insert(argThat(dbSession::equals), argThat(ruleChangeDto ->
+ ruleChangeDto.getNewCleanCodeAttribute() == CleanCodeAttribute.CLEAR
+ && ruleChangeDto.getOldCleanCodeAttribute() == CleanCodeAttribute.TESTED
+ && ruleChangeDto.getRuleUuid().equals(RULE_UUID)
+ && ruleChangeDto.getRuleImpactChangeDtos().isEmpty()));
+ }
+
+ @Test
+ public void updateWithoutCommit_whenTwoRulesChangedTheirImpactsAndAttributes_thenInsertRuleChangeAndImpactChange() {
+ PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate();
+ pluginRuleUpdate.setNewCleanCodeAttribute(CleanCodeAttribute.CLEAR);
+ pluginRuleUpdate.setOldCleanCodeAttribute(CleanCodeAttribute.TESTED);
+ pluginRuleUpdate.setRuleUuid(RULE_UUID);
+
+ //testing here detecting the change with 2 the same software qualities
+ pluginRuleUpdate.addNewImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
+ pluginRuleUpdate.addOldImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM);
+
+ PluginRuleUpdate pluginRuleUpdate2 = new PluginRuleUpdate();
+ pluginRuleUpdate2.setNewCleanCodeAttribute(CleanCodeAttribute.EFFICIENT);
+ pluginRuleUpdate2.setOldCleanCodeAttribute(CleanCodeAttribute.DISTINCT);
+ pluginRuleUpdate2.setRuleUuid("ruleUuid2");
+
+ //testing here detecting the change with 2 the different software qualities
+ pluginRuleUpdate2.addNewImpact(SoftwareQuality.SECURITY, Severity.HIGH);
+ pluginRuleUpdate2.addOldImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM);
+
+ underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate, pluginRuleUpdate2));
+
+ ArgumentCaptor<RuleChangeDto> captor = ArgumentCaptor.forClass(RuleChangeDto.class);
+ verify(ruleChangeDao, times(2)).insert(argThat(dbSession::equals), captor.capture());
+
+ RuleChangeDto firstChange = captor.getAllValues().stream().filter(change -> change.getRuleUuid().equals(RULE_UUID)).findFirst().get();
+ RuleChangeDto secondChange = captor.getAllValues().stream().filter(change -> change.getRuleUuid().equals("ruleUuid2")).findFirst().get();
+
+ assertThat(firstChange.getNewCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
+ assertThat(firstChange.getOldCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.TESTED);
+ assertThat(firstChange.getRuleUuid()).isEqualTo(RULE_UUID);
+ assertThat(firstChange.getRuleImpactChangeDtos()).hasSize(1);
+ assertThat(firstChange.getRuleImpactChangeDtos()).extracting(RuleImpactChangeDto::getNewSoftwareQuality,
+ RuleImpactChangeDto::getOldSoftwareQuality, RuleImpactChangeDto::getOldSeverity, RuleImpactChangeDto::getNewSeverity)
+ .containsExactly(tuple(SoftwareQuality.RELIABILITY.name(), SoftwareQuality.RELIABILITY.name(), Severity.MEDIUM.name(), Severity.LOW.name()));
+
+ assertThat(secondChange.getNewCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.EFFICIENT);
+ assertThat(secondChange.getOldCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.DISTINCT);
+ assertThat(secondChange.getRuleUuid()).isEqualTo("ruleUuid2");
+ assertThat(secondChange.getRuleImpactChangeDtos()).hasSize(1);
+ assertThat(secondChange.getRuleImpactChangeDtos()).extracting(RuleImpactChangeDto::getNewSoftwareQuality,
+ RuleImpactChangeDto::getOldSoftwareQuality, RuleImpactChangeDto::getOldSeverity, RuleImpactChangeDto::getNewSeverity)
+ .containsExactly(tuple(SoftwareQuality.SECURITY.name(), SoftwareQuality.RELIABILITY.name(), Severity.MEDIUM.name(), Severity.HIGH.name()));
+ }
+
+ @Test
+ public void updateWithoutCommit_whenOneRuleBelongingToTwoQualityProfilesChanged_thenInsertOneRuleChangeAndTwoQualityProfileChanges() {
+ List<ActiveRuleDto> activeRuleDtos = List.of(
+ new ActiveRuleDto().setProfileUuid("profileUuid1").setRuleUuid(RULE_UUID),
+ new ActiveRuleDto().setProfileUuid("profileUuid2").setRuleUuid(RULE_UUID));
+ when(activeRuleDao.selectByRuleUuid(any(), any())).thenReturn(activeRuleDtos);
+
+ PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate();
+ pluginRuleUpdate.setNewCleanCodeAttribute(CleanCodeAttribute.CLEAR);
+ pluginRuleUpdate.setOldCleanCodeAttribute(CleanCodeAttribute.TESTED);
+ pluginRuleUpdate.setRuleUuid(RULE_UUID);
+
+ underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate));
+
+ verify(qualityProfileChangeDao, times(2)).insert(argThat(dbSession::equals), argThat(qProfileChangeDto ->
+ qProfileChangeDto.getChangeType().equals("UPDATED")
+ && qProfileChangeDto.getRuleChangeUuid() != null));
+ }
+} \ No newline at end of file
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/StartupRuleUpdaterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/StartupRuleUpdaterTest.java
new file mode 100644
index 00000000000..f68eb4b0b2f
--- /dev/null
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/StartupRuleUpdaterTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.server.rule.registration;
+
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rule.RuleScope;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.issue.ImpactDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class StartupRuleUpdaterTest {
+
+ private final DbClient dbClient = mock();
+ private final System2 system2 = mock();
+ private final UuidFactory uuidFactory = mock();
+ private final RuleDescriptionSectionsGeneratorResolver sectionsGeneratorResolver = mock();
+
+ private final StartupRuleUpdater underTest = new StartupRuleUpdater(dbClient, system2, uuidFactory, sectionsGeneratorResolver);
+
+ @Test
+ public void findChangesAndUpdateRule_whenCleanCodeTaxonomyChanged_shouldSetAnythingChangedToTrue() {
+ RulesDefinition.Rule ruleDef = getDefaultRuleDef();
+ when(ruleDef.cleanCodeAttribute()).thenReturn(CleanCodeAttribute.CLEAR);
+ Map<SoftwareQuality, Severity> newImpacts = Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW);
+ when(ruleDef.defaultImpacts()).thenReturn(newImpacts);
+
+ RuleDto rule = getDefaultRuleDto();
+ when(rule.getCleanCodeAttribute()).thenReturn(CleanCodeAttribute.COMPLETE);
+ Set<ImpactDto> oldImpacts = Set.of(new ImpactDto("uuid", SoftwareQuality.RELIABILITY, Severity.LOW));
+ when(rule.getDefaultImpacts()).thenReturn(oldImpacts);
+
+ StartupRuleUpdater.RuleChange changesAndUpdateRule = underTest.findChangesAndUpdateRule(ruleDef, rule);
+
+ assertTrue(changesAndUpdateRule.hasRuleDefinitionChanged());
+ assertThat(changesAndUpdateRule.getPluginRuleUpdate().getOldCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.COMPLETE);
+ assertThat(changesAndUpdateRule.getPluginRuleUpdate().getNewCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
+ assertThat(changesAndUpdateRule.getPluginRuleUpdate().getNewImpacts()).isEqualTo(newImpacts);
+ assertThat(changesAndUpdateRule.getPluginRuleUpdate().getOldImpacts()).containsEntry(SoftwareQuality.RELIABILITY, Severity.LOW);
+ }
+
+ @Test
+ public void findChangesAndUpdateRule_whenImpactsChanged_thenDontIncludeUnchangedImpacts() {
+ RulesDefinition.Rule ruleDef = getDefaultRuleDef();
+ when(ruleDef.cleanCodeAttribute()).thenReturn(CleanCodeAttribute.CLEAR);
+ Map<SoftwareQuality, Severity> newImpacts = Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW, SoftwareQuality.SECURITY, Severity.HIGH);
+ when(ruleDef.defaultImpacts()).thenReturn(newImpacts);
+
+ RuleDto rule = getDefaultRuleDto();
+ when(rule.getCleanCodeAttribute()).thenReturn(CleanCodeAttribute.COMPLETE);
+ Set<ImpactDto> oldImpacts = Set.of(new ImpactDto("uuid",
+ SoftwareQuality.RELIABILITY, Severity.LOW),
+ new ImpactDto("uuid2", SoftwareQuality.SECURITY, Severity.HIGH));
+ when(rule.getDefaultImpacts()).thenReturn(oldImpacts);
+
+ StartupRuleUpdater.RuleChange changesAndUpdateRule = underTest.findChangesAndUpdateRule(ruleDef, rule);
+
+ assertTrue(changesAndUpdateRule.hasRuleDefinitionChanged());
+ assertThat(changesAndUpdateRule.getPluginRuleUpdate().getNewImpacts()).containsOnly(Map.entry(SoftwareQuality.MAINTAINABILITY, Severity.LOW));
+ assertThat(changesAndUpdateRule.getPluginRuleUpdate().getOldImpacts()).containsOnly(Map.entry(SoftwareQuality.RELIABILITY, Severity.LOW));
+ }
+
+ @Test
+ public void findChangesAndUpdateRule_whenNoCleanCodeTaxonomyChanged_thenPluginRuleChangeShouldBeNull() {
+ RulesDefinition.Rule ruleDef = getDefaultRuleDef();
+ when(ruleDef.cleanCodeAttribute()).thenReturn(CleanCodeAttribute.COMPLETE);
+ Map<SoftwareQuality, Severity> newImpacts = Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW);
+ when(ruleDef.defaultImpacts()).thenReturn(newImpacts);
+
+ RuleDto rule = getDefaultRuleDto();
+ when(rule.getCleanCodeAttribute()).thenReturn(CleanCodeAttribute.COMPLETE);
+ Set<ImpactDto> oldImpacts = Set.of(new ImpactDto("uuid",
+ SoftwareQuality.MAINTAINABILITY, Severity.LOW));
+ when(rule.getDefaultImpacts()).thenReturn(oldImpacts);
+
+ StartupRuleUpdater.RuleChange changesAndUpdateRule = underTest.findChangesAndUpdateRule(ruleDef, rule);
+
+ assertTrue(changesAndUpdateRule.hasRuleDefinitionChanged());
+ assertThat(changesAndUpdateRule.getPluginRuleUpdate()).isNull();
+ }
+
+ private RulesDefinition.Rule getDefaultRuleDef() {
+ RulesDefinition.Rule ruleDef = mock();
+ when(ruleDef.scope()).thenReturn(RuleScope.TEST);
+ when(ruleDef.repository()).thenReturn(mock());
+ when(ruleDef.type()).thenReturn(RuleType.BUG);
+ return ruleDef;
+ }
+
+ private RuleDto getDefaultRuleDto() {
+ RuleDto ruleDto = mock();
+ when(ruleDto.getScope()).thenReturn(RuleDto.Scope.TEST);
+ return ruleDto;
+ }
+} \ No newline at end of file
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java
index 10c412010b0..66a7c5cea07 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileRulesImpl.java
@@ -104,8 +104,7 @@ public class QProfileRulesImpl implements QProfileRules {
@Override
public BulkChangeResult bulkDeactivateAndCommit(DbSession dbSession, QProfileDto profile, RuleQuery ruleQuery) {
verifyNotBuiltIn(profile);
- BulkChangeResult bulkChangeResult = doBulk(dbSession, profile, ruleQuery, (context, ruleDto) ->
- ruleActivator.deactivate(dbSession, context, ruleDto.getUuid(), false));
+ BulkChangeResult bulkChangeResult = doBulk(dbSession, profile, ruleQuery, (context, ruleDto) -> ruleActivator.deactivate(dbSession, context, ruleDto.getUuid(), false));
qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), bulkChangeResult.getChanges(), profile.getLanguage());
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
index fc0ae871797..12de893f089 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
@@ -40,6 +40,7 @@ import org.sonar.server.rule.AdvancedRuleDescriptionSectionsGenerator;
import org.sonar.server.rule.LegacyHotspotRuleDescriptionSectionsGenerator;
import org.sonar.server.rule.LegacyIssueRuleDescriptionSectionsGenerator;
import org.sonar.server.rule.registration.NewRuleCreator;
+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.RuleDescriptionSectionsGeneratorResolver;
@@ -76,6 +77,7 @@ public class PlatformLevelStartup extends PlatformLevel {
LegacyHotspotRuleDescriptionSectionsGenerator.class,
LegacyIssueRuleDescriptionSectionsGenerator.class,
RulesRegistrant.class,
+ QualityProfileChangesUpdater.class,
NewRuleCreator.class,
RulesKeyVerifier.class,
StartupRuleUpdater.class,