aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacek <jacek.poreda@sonarsource.com>2024-11-27 16:14:00 +0100
committersonartech <sonartech@sonarsource.com>2024-11-29 20:03:08 +0000
commit525a2ccd20a8893e1598e4fdf9dccf16100689dd (patch)
treed0c0192c6ae679ffbf0c1c7496c72cce344fc173
parent8f0a4b81ff51e1332032a1661c0495b933479ec2 (diff)
downloadsonarqube-525a2ccd20a8893e1598e4fdf9dccf16100689dd.tar.gz
sonarqube-525a2ccd20a8893e1598e4fdf9dccf16100689dd.zip
SONAR-23619 Add Sonar way for AI Code Quality Gate
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateConditionDaoIT.java13
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/qualitygate/QualityGateDaoIT.java15
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java3
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java11
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java9
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml7
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml20
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java4
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java6
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/BuiltInQualityGate.java34
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/SonarWayQualityGate.java63
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/builtin/package-info.java23
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/builtin/SonarWayQualityGateTest.java60
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java54
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java4
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java198
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java2
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 &lt;&gt; #{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);
}
}