diff options
author | lukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com> | 2023-10-05 17:06:41 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-10-10 20:02:44 +0000 |
commit | 9ff811fd2d3421731dfb4097e8748cee1553d2c5 (patch) | |
tree | 45d4866bd21131d61def4f67536433aaade279c2 | |
parent | 047d3846d5e415786323625cd5101a9d54b04725 (diff) | |
download | sonarqube-9ff811fd2d3421731dfb4097e8748cee1553d2c5.tar.gz sonarqube-9ff811fd2d3421731dfb4097e8748cee1553d2c5.zip |
SONAR-20548 added event for changing the CCT data for rules
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, |