diff options
author | Jacek <jacek.poreda@sonarsource.com> | 2024-11-27 16:14:00 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-11-29 20:03:08 +0000 |
commit | 525a2ccd20a8893e1598e4fdf9dccf16100689dd (patch) | |
tree | d0c0192c6ae679ffbf0c1c7496c72cce344fc173 | |
parent | 8f0a4b81ff51e1332032a1661c0495b933479ec2 (diff) | |
download | sonarqube-525a2ccd20a8893e1598e4fdf9dccf16100689dd.tar.gz sonarqube-525a2ccd20a8893e1598e4fdf9dccf16100689dd.zip |
SONAR-23619 Add Sonar way for AI Code Quality Gate
18 files changed, 415 insertions, 115 deletions
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateConditionDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateConditionDaoIT.java index 51e53b2c6d9..58e01481711 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateConditionDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateConditionDaoIT.java @@ -165,6 +165,19 @@ class QualityGateConditionDaoIT { assertThat(underTest.selectByUuid(condition3.getUuid(), dbSession)).isNull(); } + @Test + void countByQualityGateUuid_shouldReturnCorrectCount() { + insertQGCondition("1", "1"); + insertQGCondition("1", "3"); + insertQGCondition("1", "299"); + + dbTester.commit(); + + + assertThat(underTest.countByQualityGateUuid(dbSession,"1")).isEqualTo(3); + assertThat(underTest.countByQualityGateUuid(dbSession,"unknown")).isZero(); + } + private QualityGateConditionDto insertQGCondition(String qualityGateUuid) { return insertQGCondition(qualityGateUuid, secure().nextAlphabetic(2)); } diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateDaoIT.java index 86a7694729f..4c1c30b4836 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateDaoIT.java @@ -37,6 +37,7 @@ import org.sonar.db.project.ProjectDto; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.sonar.db.qualitygate.QualityGateFindingDto.PERCENT_VALUE_TYPE; import static org.sonar.db.qualitygate.QualityGateFindingDto.RATING_VALUE_TYPE; @@ -159,7 +160,8 @@ class QualityGateDaoIT { // check fields assertThat(findings).hasSize(3); - assertThat(findings.stream().map(QualityGateFindingDto::getDescription).collect(Collectors.toSet())).containsExactlyInAnyOrder(metric1.getShortName(), metric2.getShortName(), metric3.getShortName()); + assertThat(findings.stream().map(QualityGateFindingDto::getDescription).collect(Collectors.toSet())).containsExactlyInAnyOrder(metric1.getShortName(), metric2.getShortName(), + metric3.getShortName()); QualityGateFindingDto finding1 = findings.stream().filter(f -> f.getDescription().equals(metric1.getShortName())).findFirst().get(); validateQualityGateFindingFields(finding1, metric1, condition1); @@ -222,20 +224,21 @@ class QualityGateDaoIT { QualityGateDto qualityGate = qualityGateDbTester.insertQualityGate(qg -> qg.setName("Random quality gate").setBuiltIn(false)); dbSession.commit(); - QualityGateDto result = underTest.selectBuiltIn(dbSession); + List<QualityGateDto> result = underTest.selectBuiltIn(dbSession); - assertThat(result.getUuid()).isEqualTo(builtInQualityGate.getUuid()); - assertThat(result.getName()).isEqualTo(builtInQualityGate.getName()); + assertThat(result) + .extracting(QualityGateDto::getUuid, QualityGateDto::getName) + .containsExactly(tuple(builtInQualityGate.getUuid(), builtInQualityGate.getName())); } @Test - void ensureOneBuiltInQualityGate() { + void ensureOnlySonarWayQualityGatesAreBuiltIn() { String builtInQgName = "Sonar Way"; QualityGateDto builtInQualityGate = qualityGateDbTester.insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true)); QualityGateDto qualityGate1 = qualityGateDbTester.insertQualityGate(qg -> qg.setName("QG1").setBuiltIn(true)); QualityGateDto qualityGate2 = qualityGateDbTester.insertQualityGate(qg -> qg.setName("QG2")); - underTest.ensureOneBuiltInQualityGate(dbSession, builtInQgName); + underTest.ensureOnlySonarWayQualityGatesAreBuiltIn(dbSession, builtInQgName); dbSession.commit(); QualityGateDto reloaded = underTest.selectByName(dbSession, builtInQgName); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java index 33399d630d2..5195480a9d9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java @@ -58,6 +58,10 @@ public class QualityGateConditionDao implements Dao { mapper(session).deleteConditionsWithInvalidMetrics(); } + public int countByQualityGateUuid(DbSession session, String name) { + return mapper(session).countByQualityGateUuid(name); + } + private static QualityGateConditionMapper mapper(DbSession session) { return session.getMapper(QualityGateConditionMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java index 5fba86f0cf6..5779b09e1ec 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java @@ -29,6 +29,8 @@ public interface QualityGateConditionMapper { List<QualityGateConditionDto> selectAll(); + int countByQualityGateUuid(String qualityGateUuid); + void update(QualityGateConditionDto newCondition); QualityGateConditionDto selectByUuid(String uuid); @@ -38,4 +40,5 @@ public interface QualityGateConditionMapper { void deleteForQualityGate(String qGateUuid); void deleteConditionsWithInvalidMetrics(); + } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java index e76c466a6f7..713df57a0bc 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java @@ -21,6 +21,7 @@ package org.sonar.db.qualitygate; import java.util.Collection; import java.util.Date; +import java.util.List; import javax.annotation.CheckForNull; import org.apache.ibatis.session.ResultHandler; import org.sonar.core.util.UuidFactory; @@ -52,6 +53,10 @@ public class QualityGateDao implements Dao { return mapper(session).selectByName(name); } + public List<QualityGateDto> selectByNames(DbSession session, Collection<String> names) { + return mapper(session).selectByNames(names); + } + @CheckForNull public QualityGateDto selectByUuid(DbSession session, String uuid) { return mapper(session).selectByUuid(uuid); @@ -75,15 +80,15 @@ public class QualityGateDao implements Dao { mapper(session).update(qGate.setUpdatedAt(new Date())); } - public void ensureOneBuiltInQualityGate(DbSession dbSession, String builtInName) { - mapper(dbSession).ensureOneBuiltInQualityGate(builtInName); + public void ensureOnlySonarWayQualityGatesAreBuiltIn(DbSession dbSession, String... builtInName) { + mapper(dbSession).ensureOnlySonarWayQualityGatesAreBuiltIn(List.of(builtInName)); } public void selectQualityGateFindings(DbSession dbSession, String qualityGateUuid, ResultHandler<QualityGateFindingDto> handler) { mapper(dbSession).selectQualityGateFindings(qualityGateUuid, handler); } - public QualityGateDto selectBuiltIn(DbSession dbSession) { + public List<QualityGateDto> selectBuiltIn(DbSession dbSession) { return mapper(dbSession).selectBuiltIn(); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java index dfaf25a1ce2..45fd3c99427 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java @@ -32,7 +32,9 @@ public interface QualityGateMapper { QualityGateDto selectByName(String name); - QualityGateDto selectBuiltIn(); + List<QualityGateDto> selectByNames(@Param("names") Collection<String> names); + + List<QualityGateDto> selectBuiltIn(); void delete(String uuid); @@ -40,7 +42,7 @@ public interface QualityGateMapper { void update(QualityGateDto qGate); - void ensureOneBuiltInQualityGate(String builtInQualityName); + void ensureOnlySonarWayQualityGatesAreBuiltIn(@Param("names") Collection<String> sonarWayQualityGatesName); void selectQualityGateFindings(String qualityGateUuid, ResultHandler<QualityGateFindingDto> handler); @@ -49,4 +51,7 @@ public interface QualityGateMapper { QualityGateDto selectDefault(); QualityGateDto selectByProjectUuid(@Param("projectUuid") String projectUuid); + + long countByNameStarting(String name); + } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml index 1159f6ec40e..924a1409df8 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml @@ -27,6 +27,13 @@ order by qgate_uuid asc </select> + + <select id="countByQualityGateUuid" resultType="int"> + select count(1) + from quality_gate_conditions + where qgate_uuid=#{qGateUuid} + </select> + <select id="selectByUuid" parameterType="String" resultType="QualityGateCondition"> select <include refid="conditionColumns"/> diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml index a91ed6cb8d3..d4dd06bf35d 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml @@ -33,6 +33,14 @@ where name=#{name, jdbcType=VARCHAR} </select> + <select id="selectByNames" parameterType="map" resultType="QualityGate"> + select + <include refid="gateColumns"/> + from quality_gates qg + where name in + <foreach collection="names" open="(" close=")" item="name" separator=",">#{name, jdbcType=VARCHAR}</foreach> + </select> + <select id="selectByUuid" parameterType="String" resultType="QualityGate"> select <include refid="gateColumns"/> @@ -40,7 +48,7 @@ where uuid=#{uuid, jdbcType=VARCHAR} </select> - <select id="selectByProjectUuid" parameterType="Map" resultType="org.sonar.db.qualitygate.QualityGateDto"> + <select id="selectByProjectUuid" parameterType="Map" resultType="QualityGate"> SELECT <include refid="gateColumns"/> FROM @@ -49,7 +57,7 @@ project_qgates pqg ON pqg.quality_gate_uuid = qg.uuid AND pqg.project_uuid = #{projectUuid, jdbcType=VARCHAR} </select> - <select id="selectBuiltIn" resultType="org.sonar.db.qualitygate.QualityGateDto"> + <select id="selectBuiltIn" resultType="QualityGate"> SELECT <include refid="gateColumns"/> FROM quality_gates qg @@ -57,7 +65,7 @@ is_built_in = ${_true} </select> - <select id="selectDefault" resultType="org.sonar.db.qualitygate.QualityGateDto"> + <select id="selectDefault" resultType="QualityGate"> SELECT <include refid="gateColumns"/> FROM quality_gates qg INNER JOIN properties p ON qg.uuid = p.text_value @@ -95,12 +103,14 @@ where uuid=#{uuid, jdbcType=VARCHAR} </update> - <update id="ensureOneBuiltInQualityGate" parameterType="string"> + <update id="ensureOnlySonarWayQualityGatesAreBuiltIn" parameterType="string"> UPDATE quality_gates SET is_built_in=${_false} WHERE - is_built_in=${_true} AND name <> #{builtInQualityName, jdbcType=VARCHAR} + is_built_in=${_true} + AND name not in + <foreach collection="names" open="(" close=")" item="name" separator=",">#{name, jdbcType=VARCHAR}</foreach> </update> </mapper> diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java index c4ccfd82f3a..9a60dd26e57 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java @@ -29,10 +29,6 @@ import static java.util.Objects.requireNonNull; @Immutable public class QualityGate { - public static final String BUILTIN_QUALITY_GATE_NAME = "Sonar way"; - - public static final String SONAR_WAY_LEGACY_QUALITY_GATE_NAME = "Sonar way (legacy)"; - private final String id; private final String name; private final Set<Condition> conditions; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java index 1090806fcad..4deee6f148b 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java @@ -24,9 +24,9 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.project.ProjectDto; import org.sonar.db.qualitygate.QualityGateDto; +import org.sonar.server.qualitygate.builtin.SonarWayQualityGate; import static java.lang.String.format; -import static org.sonar.server.qualitygate.QualityGate.BUILTIN_QUALITY_GATE_NAME; public class QualityGateFinder { private final DbClient dbClient; @@ -59,8 +59,8 @@ public class QualityGateFinder { } public QualityGateDto getSonarWay(DbSession dbSession) { - return Optional.ofNullable(dbClient.qualityGateDao().selectByName(dbSession, BUILTIN_QUALITY_GATE_NAME)).orElseThrow(() -> - new IllegalStateException(format("%s quality gate is missing", BUILTIN_QUALITY_GATE_NAME))); + return Optional.ofNullable(dbClient.qualityGateDao().selectByName(dbSession, SonarWayQualityGate.NAME)).orElseThrow(() -> + new IllegalStateException(format("%s quality gate is missing", SonarWayQualityGate.NAME))); } public static class QualityGateData { diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/BuiltInQualityGate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/BuiltInQualityGate.java new file mode 100644 index 00000000000..1dae028e10c --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/BuiltInQualityGate.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate.builtin; + + +import java.util.List; +import org.sonar.server.qualitygate.Condition; + +public interface BuiltInQualityGate { + + String getName(); + + boolean supportsAiCode(); + + List<Condition> getConditions(); + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/SonarWayQualityGate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/SonarWayQualityGate.java new file mode 100644 index 00000000000..92b566343d1 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/SonarWayQualityGate.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate.builtin; + +import java.util.List; +import org.sonar.api.server.ServerSide; +import org.sonar.server.qualitygate.Condition; + +import static java.util.Arrays.asList; +import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; +import static org.sonar.server.qualitygate.Condition.Operator.GREATER_THAN; +import static org.sonar.server.qualitygate.Condition.Operator.LESS_THAN; + +@ServerSide +public class SonarWayQualityGate implements BuiltInQualityGate { + + public static final String NAME = "Sonar way"; + + private final List<Condition> conditions; + + public SonarWayQualityGate() { + this.conditions = asList( + new Condition(NEW_VIOLATIONS_KEY, GREATER_THAN, "0"), + new Condition(NEW_COVERAGE_KEY, LESS_THAN, "80"), + new Condition(NEW_DUPLICATED_LINES_DENSITY_KEY, GREATER_THAN, "3"), + new Condition(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, LESS_THAN, "100")); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean supportsAiCode() { + return false; + } + + @Override + public List<Condition> getConditions() { + return conditions; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/package-info.java new file mode 100644 index 00000000000..1c8fad2807f --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.qualitygate.builtin; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/builtin/SonarWayQualityGateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/builtin/SonarWayQualityGateTest.java new file mode 100644 index 00000000000..a632fbfadfb --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/builtin/SonarWayQualityGateTest.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate.builtin; + +import org.junit.jupiter.api.Test; +import org.sonar.server.qualitygate.Condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; +import static org.sonar.server.qualitygate.Condition.Operator.GREATER_THAN; +import static org.sonar.server.qualitygate.Condition.Operator.LESS_THAN; + +class SonarWayQualityGateTest { + + SonarWayQualityGate underTest = new SonarWayQualityGate(); + + @Test + void getName() { + assertThat(underTest.getName()) + .isEqualTo("Sonar way"); + } + + @Test + void supportAiCode_shouldReturnTrue() { + assertThat(underTest.supportsAiCode()) + .isFalse(); + } + + @Test + void getConditions_shouldReturnNewCodeOnly() { + assertThat(underTest.getConditions()) + .containsExactlyInAnyOrder( + new Condition(NEW_VIOLATIONS_KEY, GREATER_THAN, "0"), + new Condition(NEW_COVERAGE_KEY, LESS_THAN, "80"), + new Condition(NEW_DUPLICATED_LINES_DENSITY_KEY, GREATER_THAN, "3"), + new Condition(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, LESS_THAN, "100")); + + } + +} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java index 9c421b23dde..4feda68dd67 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java @@ -32,7 +32,7 @@ import org.junit.runner.RunWith; import org.slf4j.event.Level; import org.sonar.api.testfixtures.log.LogTester; import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactoryFast; +import org.sonar.core.util.SequenceUuidFactory; import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -43,6 +43,8 @@ import org.sonar.db.qualitygate.QualityGateConditionDao; import org.sonar.db.qualitygate.QualityGateConditionDto; import org.sonar.db.qualitygate.QualityGateDao; import org.sonar.db.qualitygate.QualityGateDto; +import org.sonar.server.qualitygate.builtin.BuiltInQualityGate; +import org.sonar.server.qualitygate.builtin.SonarWayQualityGate; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -58,8 +60,6 @@ import static org.sonar.api.measures.Metric.ValueType.PERCENT; import static org.sonar.db.metric.MetricTesting.newMetricDto; import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_GREATER_THAN; import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_LESS_THAN; -import static org.sonar.server.qualitygate.QualityGate.BUILTIN_QUALITY_GATE_NAME; -import static org.sonar.server.qualitygate.QualityGate.SONAR_WAY_LEGACY_QUALITY_GATE_NAME; @RunWith(DataProviderRunner.class) public class RegisterQualityGatesIT { @@ -76,8 +76,11 @@ public class RegisterQualityGatesIT { private final MetricDao metricDao = dbClient.metricDao(); private final QualityGateConditionsUpdater qualityGateConditionsUpdater = new QualityGateConditionsUpdater(dbClient); - private final RegisterQualityGates underTest = new RegisterQualityGates(dbClient, qualityGateConditionsUpdater, - UuidFactoryFast.getInstance(), System2.INSTANCE); + private final RegisterQualityGates underTest = new RegisterQualityGates(dbClient, + new BuiltInQualityGate[] {new SonarWayQualityGate()}, + qualityGateConditionsUpdater, + new SequenceUuidFactory(), + System2.INSTANCE); @Test public void register_default_gate() { @@ -87,9 +90,9 @@ public class RegisterQualityGatesIT { verifyCorrectBuiltInQualityGate(); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate [Sonar way] has been created"); + logTester.logs(Level.INFO)).contains("Quality Gate [Sonar way] has been created"); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); + logTester.logs(Level.INFO)).contains("Quality Gate's conditions of [Sonar way] has been updated"); } @Test @@ -101,7 +104,7 @@ public class RegisterQualityGatesIT { assertThat(db.countRowsOfTable("quality_gates")).isOne(); verifyCorrectBuiltInQualityGate(); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); + logTester.logs(Level.INFO)).contains("Quality Gate's conditions of [Sonar way] has been updated"); } @Test @@ -120,7 +123,7 @@ public class RegisterQualityGatesIT { verifyCorrectBuiltInQualityGate(); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); + logTester.logs(Level.INFO)).contains("Quality Gate's conditions of [Sonar way] has been updated"); } @Test @@ -136,7 +139,7 @@ public class RegisterQualityGatesIT { verifyCorrectBuiltInQualityGate(); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); + logTester.logs(Level.INFO)).contains("Quality Gate's conditions of [Sonar way] has been updated"); } @Test @@ -153,7 +156,7 @@ public class RegisterQualityGatesIT { verifyCorrectBuiltInQualityGate(); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); + logTester.logs(Level.INFO)).contains("Quality Gate's conditions of [Sonar way] has been updated"); } @Test @@ -172,7 +175,7 @@ public class RegisterQualityGatesIT { verifyCorrectBuiltInQualityGate(); assertThat( - logTester.logs(Level.INFO)).contains("Quality gate [Sonar way] has been set as built-in"); + logTester.logs(Level.INFO)).contains("Quality Gate [Sonar way] builtin flag has been updated to [true]"); } @Test @@ -204,15 +207,16 @@ public class RegisterQualityGatesIT { underTest.start(); - QualityGateDto oldQualityGate = qualityGateDao.selectByName(dbSession, qualityGateName); + QualityGateDto oldQualityGate = qualityGateDao.selectByUuid(dbSession, builtin.getUuid()); assertThat(oldQualityGate).isNotNull(); + assertThat(oldQualityGate.getName()).startsWith("IncorrectQualityGate (backup from"); assertThat(oldQualityGate.isBuiltIn()).isFalse(); - var qualityGateDto = qualityGateDao.selectByName(dbSession, BUILTIN_QUALITY_GATE_NAME); + var qualityGateDto = qualityGateDao.selectByName(dbSession, SonarWayQualityGate.NAME); assertThat(qualityGateDto).isNotNull(); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate [Sonar way] has been created"); + logTester.logs(Level.INFO)).contains("Quality Gate [Sonar way] has been created"); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); + logTester.logs(Level.INFO)).contains("Quality Gate's conditions of [Sonar way] has been updated"); } @Test @@ -232,25 +236,25 @@ public class RegisterQualityGatesIT { // No exception thrown verifyCorrectBuiltInQualityGate(); assertThat( - logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); + logTester.logs(Level.INFO)).contains("Quality Gate's conditions of [Sonar way] has been updated"); } @Test public void register_sonar_way_legacy_qg_if_not_exists_and_existing_instance() { insertMetrics(); - QualityGateDto builtin = new QualityGateDto().setName(BUILTIN_QUALITY_GATE_NAME).setBuiltIn(true).setUuid(Uuids.createFast()); + QualityGateDto builtin = new QualityGateDto().setName(SonarWayQualityGate.NAME).setBuiltIn(true).setUuid(Uuids.createFast()); qualityGateDao.insert(dbSession, builtin); createBuiltInConditions(builtin); dbSession.commit(); underTest.start(); - var qualityGateDto = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME); + var qualityGateDto = qualityGateDao.selectByName(dbSession, RegisterQualityGates.SONAR_WAY_LEGACY_NAME); assertThat(qualityGateDto).isNotNull(); verifyCorrectSonarWayLegacyQualityGate(); assertThat( - logTester.logs(Level.INFO)).contains("Sonar way (legacy) quality gate has been created"); + logTester.logs(Level.INFO)).contains("Sonar way (legacy) Quality Gate has been created"); } @@ -258,20 +262,20 @@ public class RegisterQualityGatesIT { @UseDataProvider("data") public void do_not_register_sonar_way_legacy_qg(boolean isNewInstance, boolean hasSonarWayLegacyQG) { insertMetrics(); - QualityGateDto builtin = new QualityGateDto().setName(BUILTIN_QUALITY_GATE_NAME).setBuiltIn(true).setUuid(Uuids.createFast()); + QualityGateDto builtin = new QualityGateDto().setName(SonarWayQualityGate.NAME).setBuiltIn(true).setUuid(Uuids.createFast()); qualityGateDao.insert(dbSession, builtin); if (!isNewInstance) { createBuiltInConditions(builtin); } if (hasSonarWayLegacyQG) { - QualityGateDto sonarWayLegacy = new QualityGateDto().setName(SONAR_WAY_LEGACY_QUALITY_GATE_NAME).setBuiltIn(true).setUuid(Uuids.createFast()); + QualityGateDto sonarWayLegacy = new QualityGateDto().setName(RegisterQualityGates.SONAR_WAY_LEGACY_NAME).setBuiltIn(false).setUuid(Uuids.createFast()); qualityGateDao.insert(dbSession, sonarWayLegacy); } dbSession.commit(); underTest.start(); - var qualityGateDto = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME); + var qualityGateDto = qualityGateDao.selectByName(dbSession, RegisterQualityGates.SONAR_WAY_LEGACY_NAME); if (hasSonarWayLegacyQG) { assertThat(qualityGateDto).isNotNull(); } else { @@ -315,7 +319,7 @@ public class RegisterQualityGatesIT { MetricDto newDuplication = metricDao.selectByKey(dbSession, NEW_DUPLICATED_LINES_DENSITY_KEY); MetricDto newSecurityHotspots = metricDao.selectByKey(dbSession, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY); - QualityGateDto qualityGateDto = qualityGateDao.selectByName(dbSession, BUILTIN_QUALITY_GATE_NAME); + QualityGateDto qualityGateDto = qualityGateDao.selectByName(dbSession, SonarWayQualityGate.NAME); assertThat(qualityGateDto).isNotNull(); assertThat(qualityGateDto.getCreatedAt()).isNotNull(); assertThat(qualityGateDto.isBuiltIn()).isTrue(); @@ -336,7 +340,7 @@ public class RegisterQualityGatesIT { MetricDto newCoverage = metricDao.selectByKey(dbSession, NEW_COVERAGE_KEY); MetricDto newDuplication = metricDao.selectByKey(dbSession, NEW_DUPLICATED_LINES_DENSITY_KEY); MetricDto newSecurityHotspots = metricDao.selectByKey(dbSession, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY); - QualityGateDto qualityGateDto = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME); + QualityGateDto qualityGateDto = qualityGateDao.selectByName(dbSession, RegisterQualityGates.SONAR_WAY_LEGACY_NAME); assertThat(qualityGateDto).isNotNull(); assertThat(qualityGateDto.getCreatedAt()).isNotNull(); assertThat(qualityGateDto.isBuiltIn()).isFalse(); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java index 24c4eb83a21..a1d5fbc08c1 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java @@ -20,6 +20,7 @@ package org.sonar.server.qualitygate; import org.sonar.core.platform.Module; +import org.sonar.server.qualitygate.builtin.SonarWayQualityGate; public class QualityGateModule extends Module { @Override @@ -31,6 +32,7 @@ public class QualityGateModule extends Module { QualityGateConditionsUpdater.class, QualityGateFinder.class, QualityGateEvaluatorImpl.class, - QualityGateFallbackManager.class); + QualityGateFallbackManager.class, + SonarWayQualityGate.class); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java index 6868ef04f86..fb019348f22 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java @@ -19,16 +19,24 @@ */ package org.sonar.server.qualitygate; -import com.google.common.collect.ImmutableList; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.CheckForNull; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.Startable; @@ -42,8 +50,9 @@ import org.sonar.db.qualitygate.QualityGateConditionDto; import org.sonar.db.qualitygate.QualityGateDao; import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.server.measure.Rating; +import org.sonar.server.qualitygate.builtin.BuiltInQualityGate; +import org.sonar.server.qualitygate.builtin.SonarWayQualityGate; -import static com.google.common.collect.ImmutableList.toImmutableList; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toMap; import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; @@ -52,40 +61,28 @@ import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_GREATER_THAN; import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_LESS_THAN; -import static org.sonar.server.qualitygate.QualityGate.BUILTIN_QUALITY_GATE_NAME; -import static org.sonar.server.qualitygate.QualityGate.SONAR_WAY_LEGACY_QUALITY_GATE_NAME; public class RegisterQualityGates implements Startable { - private static final Logger LOGGER = LoggerFactory.getLogger(RegisterQualityGates.class); + static final String SONAR_WAY_LEGACY_NAME = "Sonar way (legacy)"; - private static final String A_RATING = Integer.toString(Rating.A.getIndex()); - private static final List<QualityGateCondition> BUILT_IN_QUALITY_GATE_CONDITIONS = asList( - new QualityGateCondition().setMetricKey(NEW_VIOLATIONS_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold("0"), - new QualityGateCondition().setMetricKey(NEW_COVERAGE_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("80"), - new QualityGateCondition().setMetricKey(NEW_DUPLICATED_LINES_DENSITY_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold("3"), - new QualityGateCondition().setMetricKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("100")); - - private static final List<QualityGateCondition> SONAR_WAY_LEGACY_QUALITY_GATE_CONDITIONS = asList( - new QualityGateCondition().setMetricKey(NEW_SECURITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(A_RATING), - new QualityGateCondition().setMetricKey(NEW_RELIABILITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(A_RATING), - new QualityGateCondition().setMetricKey(NEW_MAINTAINABILITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(A_RATING), - new QualityGateCondition().setMetricKey(NEW_COVERAGE_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("80"), - new QualityGateCondition().setMetricKey(NEW_DUPLICATED_LINES_DENSITY_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold("3"), - new QualityGateCondition().setMetricKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("100")); + private static final Logger LOGGER = LoggerFactory.getLogger(RegisterQualityGates.class); private final DbClient dbClient; + private final Map<String, BuiltInQualityGate> builtInQualityGateMap; private final QualityGateConditionsUpdater qualityGateConditionsUpdater; private final QualityGateDao qualityGateDao; private final QualityGateConditionDao qualityGateConditionDao; private final UuidFactory uuidFactory; private final System2 system2; - public RegisterQualityGates(DbClient dbClient, QualityGateConditionsUpdater qualityGateConditionsUpdater, UuidFactory uuidFactory, System2 system2) { + public RegisterQualityGates(DbClient dbClient, BuiltInQualityGate[] builtInQualityGates, QualityGateConditionsUpdater qualityGateConditionsUpdater, + UuidFactory uuidFactory, System2 system2) { this.dbClient = dbClient; + this.builtInQualityGateMap = Stream.of(builtInQualityGates) + .collect(Collectors.toMap(BuiltInQualityGate::getName, Function.identity())); this.qualityGateConditionsUpdater = qualityGateConditionsUpdater; this.qualityGateDao = dbClient.qualityGateDao(); this.qualityGateConditionDao = dbClient.gateConditionDao(); @@ -96,49 +93,85 @@ public class RegisterQualityGates implements Startable { @Override public void start() { try (DbSession dbSession = dbClient.openSession(false)) { - QualityGateDto builtinQualityGate = qualityGateDao.selectByName(dbSession, BUILTIN_QUALITY_GATE_NAME); + List<QualityGateDto> builtinQualityGates = qualityGateDao.selectByNames(dbSession, builtInQualityGateMap.keySet()); - // Create builtinQualityGate if not present - if (builtinQualityGate == null) { - LOGGER.info("Built-in quality gate [{}] has been created", BUILTIN_QUALITY_GATE_NAME); - builtinQualityGate = createQualityGate(dbSession, BUILTIN_QUALITY_GATE_NAME, true); - } - List<QualityGateCondition> builtInQualityGateConditions = getQualityGateConditions(dbSession, builtinQualityGate); + List<QualityGateDto> newBuiltinQualityGates = createBuiltInQualityGates(dbSession, builtinQualityGates); + builtinQualityGates.addAll(newBuiltinQualityGates); - // Create sonar way (legacy) only if it is not a new instance (a new instance has a Sonar way QG and no conditions) and if it is - // not already present - if (!builtInQualityGateConditions.isEmpty()) { - createSonarWayLegacyQualityGateIfMissing(dbSession); - } + createLegacyQualityGate(dbSession, builtinQualityGates); + updateQualityGates(dbSession, builtinQualityGates); + cleanupQualityGates(dbSession); + dbSession.commit(); + } + } - // Set builtinQualityGate if missing - if (!builtinQualityGate.isBuiltIn()) { - builtinQualityGate.setBuiltIn(true); - dbClient.qualityGateDao().update(builtinQualityGate, dbSession); - LOGGER.info("Quality gate [{}] has been set as built-in", BUILTIN_QUALITY_GATE_NAME); - } + private List<QualityGateDto> createBuiltInQualityGates(DbSession dbSession, List<QualityGateDto> builtinQualityGates) { + Set<String> builtinQualityGatesFromDB = builtinQualityGates.stream().map(QualityGateDto::getName).collect(Collectors.toSet()); - updateQualityConditionsIfRequired(dbSession, builtinQualityGate, builtInQualityGateConditions); + Set<String> qualityGatesToBeCreated = new HashSet<>(builtInQualityGateMap.keySet()); + qualityGatesToBeCreated.removeAll(builtinQualityGatesFromDB); - qualityGateDao.ensureOneBuiltInQualityGate(dbSession, BUILTIN_QUALITY_GATE_NAME); + return qualityGatesToBeCreated + .stream().map(name -> { + BuiltInQualityGate builtInQualityGateDef = builtInQualityGateMap.get(name); + QualityGateDto qualityGate = createQualityGate(dbSession, builtInQualityGateDef.getName(), true, builtInQualityGateDef.supportsAiCode()); + LOGGER.info("Quality Gate [{}] has been created", qualityGate.getName()); + return qualityGate; + }) + .toList(); + } - dbSession.commit(); + private void createLegacyQualityGate(DbSession dbSession, List<QualityGateDto> builtinQualityGates) { + Optional<QualityGateDto> existingSonarWay = builtinQualityGates.stream().filter(qg -> SonarWayQualityGate.NAME.equals(qg.getName())).findFirst(); + + if (existingSonarWay.isEmpty()) { + return; } - } - private void createSonarWayLegacyQualityGateIfMissing(DbSession dbSession) { - QualityGateDto sonarWayLegacyQualityGate = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME); - LOGGER.info("Sonar way legacy Gate: {} ", sonarWayLegacyQualityGate); + // Create sonar way (legacy) only if it is not a new instance (a new instance has a Sonar way QG and no conditions) and if it is + // not already present + // FIXME:: The logic explained above doesn't make any sense as after upgrade - legacy Quality Gate will created, + // FIXME:: There is open bug ticket to address this issue: SONAR-23753 + boolean shouldCreateLegacy = qualityGateConditionDao.countByQualityGateUuid(dbSession, existingSonarWay.get().getUuid()) > 0; + + if (!shouldCreateLegacy) { + return; + } + + QualityGateDto sonarWayLegacyQualityGate = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_NAME); + if (sonarWayLegacyQualityGate == null) { - sonarWayLegacyQualityGate = createQualityGate(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME, false); - addConditionsToQualityGate(dbSession, sonarWayLegacyQualityGate, SONAR_WAY_LEGACY_QUALITY_GATE_CONDITIONS); - LOGGER.info("Sonar way (legacy) quality gate has been created"); + sonarWayLegacyQualityGate = createQualityGate(dbSession, SONAR_WAY_LEGACY_NAME, false, false); + addConditionsToQualityGate(dbSession, sonarWayLegacyQualityGate, asList( + new QualityGateCondition().setMetricKey(NEW_SECURITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(Integer.toString(Rating.A.getIndex())), + new QualityGateCondition().setMetricKey(NEW_RELIABILITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(Integer.toString(Rating.A.getIndex())), + new QualityGateCondition().setMetricKey(NEW_MAINTAINABILITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(Integer.toString(Rating.A.getIndex())), + new QualityGateCondition().setMetricKey(NEW_COVERAGE_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("80"), + new QualityGateCondition().setMetricKey(NEW_DUPLICATED_LINES_DENSITY_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold("3"), + new QualityGateCondition().setMetricKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("100"))); + LOGGER.info("Sonar way (legacy) Quality Gate has been created"); + } else { + LOGGER.info("Sonar way legacy Gate uuid: {} ", sonarWayLegacyQualityGate.getUuid()); } } - private void updateQualityConditionsIfRequired(DbSession dbSession, QualityGateDto builtinQualityGate, - List<QualityGateCondition> qualityGateConditions) { + private void updateQualityGates(DbSession dbSession, List<QualityGateDto> builtinQualityGates) { + Map<String, String> uuidToKeyMetric = dbClient.metricDao().selectAll(dbSession).stream() + .collect(toMap(MetricDto::getUuid, MetricDto::getKey)); + + builtinQualityGates.forEach(qualityGate -> { + updateQualityConditionsIfRequired(dbSession, qualityGate, uuidToKeyMetric); + if (!qualityGate.isBuiltIn()) { + qualityGate.setBuiltIn(true); + qualityGateDao.update(qualityGate, dbSession); + LOGGER.info("Quality Gate [{}] builtin flag has been updated to [{}]", qualityGate.getName(), true); + } + }); + } + + private void updateQualityConditionsIfRequired(DbSession dbSession, QualityGateDto builtinQualityGate, Map<String, String> uuidToKeyMetric) { + List<QualityGateCondition> qualityGateConditions = getQualityGateConditions(dbSession, builtinQualityGate, uuidToKeyMetric); List<QualityGateCondition> qgConditionsDeleted = removeExtraConditions(dbSession, builtinQualityGate, qualityGateConditions); qgConditionsDeleted.addAll(removeDuplicatedConditions(dbSession, builtinQualityGate, qualityGateConditions)); @@ -146,25 +179,26 @@ public class RegisterQualityGates implements Startable { List<QualityGateCondition> qgConditionsAdded = addMissingConditions(dbSession, builtinQualityGate, qualityGateConditions); if (!qgConditionsAdded.isEmpty() || !qgConditionsDeleted.isEmpty()) { - LOGGER.info("Built-in quality gate's conditions of [{}] has been updated", BUILTIN_QUALITY_GATE_NAME); + LOGGER.info("Quality Gate's conditions of [{}] has been updated", builtinQualityGate.getName()); } } - private ImmutableList<QualityGateCondition> getQualityGateConditions(DbSession dbSession, QualityGateDto builtinQualityGate) { - Map<String, String> uuidToKeyMetric = dbClient.metricDao().selectAll(dbSession).stream() - .collect(toMap(MetricDto::getUuid, MetricDto::getKey)); - + private List<QualityGateCondition> getQualityGateConditions(DbSession dbSession, QualityGateDto builtinQualityGate, Map<String, String> uuidToKeyMetric) { return qualityGateConditionDao.selectForQualityGate(dbSession, builtinQualityGate.getUuid()) .stream() .map(dto -> QualityGateCondition.from(dto, uuidToKeyMetric)) - .collect(toImmutableList()); + .toList(); } private List<QualityGateCondition> removeExtraConditions(DbSession dbSession, QualityGateDto builtinQualityGate, List<QualityGateCondition> qualityGateConditions) { - // Find all conditions that are not present in QUALITY_GATE_CONDITIONS - // Those conditions must be deleted List<QualityGateCondition> qgConditionsToBeDeleted = new ArrayList<>(qualityGateConditions); - qgConditionsToBeDeleted.removeAll(BUILT_IN_QUALITY_GATE_CONDITIONS); + List<QualityGateCondition> gateConditions = builtInQualityGateMap.get(builtinQualityGate.getName()).getConditions().stream() + .map(conditionDef -> new QualityGateCondition() + .setMetricKey(conditionDef.getMetricKey()) + .setOperator(conditionDef.getOperator().getDbValue()) + .setErrorThreshold(conditionDef.getErrorThreshold())) + .toList(); + qgConditionsToBeDeleted.removeAll(gateConditions); qgConditionsToBeDeleted .forEach(qgc -> qualityGateConditionDao.delete(qgc.toQualityGateDto(builtinQualityGate.getUuid()), dbSession)); return qgConditionsToBeDeleted; @@ -184,9 +218,13 @@ public class RegisterQualityGates implements Startable { private List<QualityGateCondition> addMissingConditions(DbSession dbSession, QualityGateDto builtinQualityGate, List<QualityGateCondition> qualityGateConditions) { - // Find all conditions that are not present in qualityGateConditions - // Those conditions must be added to the built-in quality gate - List<QualityGateCondition> qgConditionsToBeAdded = new ArrayList<>(BUILT_IN_QUALITY_GATE_CONDITIONS); + List<QualityGateCondition> gateConditions = builtInQualityGateMap.get(builtinQualityGate.getName()).getConditions().stream() + .map(conditionDef -> new QualityGateCondition() + .setMetricKey(conditionDef.getMetricKey()) + .setOperator(conditionDef.getOperator().getDbValue()) + .setErrorThreshold(conditionDef.getErrorThreshold())) + .toList(); + List<QualityGateCondition> qgConditionsToBeAdded = new ArrayList<>(gateConditions); qgConditionsToBeAdded.removeAll(qualityGateConditions); addConditionsToQualityGate(dbSession, builtinQualityGate, qgConditionsToBeAdded); return qgConditionsToBeAdded; @@ -198,18 +236,48 @@ public class RegisterQualityGates implements Startable { condition.getErrorThreshold())); } + private void cleanupQualityGates(DbSession dbSession) { + List<QualityGateDto> qualityGateDtos = qualityGateDao.selectBuiltIn(dbSession); + for (QualityGateDto builtinQualityGate : qualityGateDtos) { + String oldName = builtinQualityGate.getName(); + if (!builtInQualityGateMap.containsKey(oldName)) { + String newName = generateNewName(oldName); + builtinQualityGate + .setName(newName) + .setAiCodeSupported(false) + .setBuiltIn(false); + qualityGateDao.update(builtinQualityGate, dbSession); + LOGGER.info("Quality Gate [{}] has been unset as builtin and renamed to: [{}]", oldName, newName); + } + } + } + + /** + * Abbreviate Quality Gate name if it will be too long with prefix and append suffix + */ + private String generateNewName(String name) { + var shortName = StringUtils.abbreviate(name, 40); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM dd yyyy 'at' hh:mm a") + .withLocale(Locale.getDefault()) + .withZone(ZoneId.systemDefault()); + var now = formatter.format(Instant.ofEpochMilli(system2.now())); + String suffix = " (backup from " + now + ")"; + return shortName + suffix; + } + @Override public void stop() { // do nothing } - private QualityGateDto createQualityGate(DbSession dbSession, String name, boolean isBuiltIn) { + private QualityGateDto createQualityGate(DbSession dbSession, String name, boolean isBuiltIn, boolean aiCodeSupported) { QualityGateDto qualityGate = new QualityGateDto() .setName(name) .setBuiltIn(isBuiltIn) + .setAiCodeSupported(aiCodeSupported) .setUuid(uuidFactory.create()) .setCreatedAt(new Date(system2.now())); - return dbClient.qualityGateDao().insert(dbSession, qualityGate); + return qualityGateDao.insert(dbSession, qualityGate); } private static class QualityGateCondition { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java index 4579de0b936..0ae9d8d70af 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java @@ -29,6 +29,6 @@ public class QualityGateModuleTest { public void verify_count_of_added_components() { ListContainer container = new ListContainer(); new QualityGateModule().configure(container); - assertThat(container.getAddedObjects()).hasSize(7); + assertThat(container.getAddedObjects()).hasSize(8); } } |