diff options
author | Alain Kermis <alain.kermis@sonarsource.com> | 2023-01-12 14:19:38 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-01-13 20:02:46 +0000 |
commit | 7166f4eb28690770382e442a4903801b96facd05 (patch) | |
tree | 1467de9f9ed09f4b62c498f201657d2b14427aee /server | |
parent | 27d238057534025c145e233de36f419f3dce66ed (diff) | |
download | sonarqube-7166f4eb28690770382e442a4903801b96facd05.tar.gz sonarqube-7166f4eb28690770382e442a4903801b96facd05.zip |
SONAR-18188 Share CAYC quality gate info via telemetry
Diffstat (limited to 'server')
11 files changed, 329 insertions, 110 deletions
diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index 4e2bcd598a0..058a80af47d 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -81,6 +81,7 @@ public final class SqTables { "project_mappings", "project_measures", "project_qprofiles", + "project_qgates", "properties", "push_events", "qprofile_changes", diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java index 086d1025a45..205c36d64af 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java @@ -30,6 +30,10 @@ public class ProjectQgateAssociationDao implements Dao { return mapper(dbSession).selectProjects(query); } + public List<ProjectQgateAssociationDto> selectAll(DbSession dbSession) { + return mapper(dbSession).selectAll(); + } + /** * @return quality gate uuid if a specific Quality Gate has been defined for the given project uuid. <br> * Returns <code>{@link Optional#empty()}</code> otherwise (ex: default quality gate applies) diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java index 97bfdebf66c..931247f3bde 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java @@ -27,6 +27,8 @@ public interface ProjectQgateAssociationMapper { List<ProjectQgateAssociationDto> selectProjects(@Param("query") ProjectQgateAssociationQuery query); + List<ProjectQgateAssociationDto> selectAll(); + @CheckForNull String selectQGateUuidByProjectUuid(String projectUuid); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml index 4419b062141..b71d11746b7 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml @@ -27,6 +27,11 @@ order by proj.name, proj.kee </select> + <select id="selectAll" resultType="ProjectQgateAssociation"> + SELECT project_uuid as uuid, quality_gate_uuid as gateUuid + FROM project_qgates + </select> + <select id="selectQGateUuidByProjectUuid" parameterType="String" resultType="string"> SELECT quality_gate_uuid FROM project_qgates diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java index d57404aff02..d03b6c59e3a 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java @@ -146,6 +146,35 @@ public class ProjectQgateAssociationDaoTest { } @Test + public void select_all() { + List<ProjectQgateAssociationDto> t = underTest.selectAll(dbSession); + + QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate(); + QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate(); + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + ComponentDto project3 = db.components().insertPrivateProject(); + ComponentDto project4 = db.components().insertPrivateProject(); + ComponentDto project5 = db.components().insertPrivateProject(); + db.qualityGates().associateProjectToQualityGate(db.components().getProjectDto(project1), qualityGate1); + db.qualityGates().associateProjectToQualityGate(db.components().getProjectDto(project2), qualityGate2); + db.qualityGates().associateProjectToQualityGate(db.components().getProjectDto(project3), qualityGate1); + db.qualityGates().associateProjectToQualityGate(db.components().getProjectDto(project4), qualityGate2); + db.qualityGates().associateProjectToQualityGate(db.components().getProjectDto(project5), qualityGate1); + + List<ProjectQgateAssociationDto> result = underTest.selectAll(dbSession); + + assertThat(result) + .extracting(ProjectQgateAssociationDto::getUuid, ProjectQgateAssociationDto::getGateUuid) + .containsExactlyInAnyOrder( + tuple(project1.uuid(), qualityGate1.getUuid()), + tuple(project2.uuid(), qualityGate2.getUuid()), + tuple(project3.uuid(), qualityGate1.getUuid()), + tuple(project4.uuid(), qualityGate2.getUuid()), + tuple(project5.uuid(), qualityGate1.getUuid())); + } + + @Test public void select_qgate_uuid_is_absent() { ComponentDto project = db.components().insertPrivateProject(); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java index 61f4dc47e43..6daa1256f8a 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java @@ -39,6 +39,7 @@ public class TelemetryData { private final Map<String, String> plugins; private final Database database; private final EditionProvider.Edition edition; + private final String defaultQualityGate; private final Long installationDate; private final String installationVersion; private final boolean inDocker; @@ -46,6 +47,7 @@ public class TelemetryData { private final List<UserTelemetryDto> users; private final List<Project> projects; private final List<ProjectStatistics> projectStatistics; + private final List<QualityGate> qualityGates; private final Boolean hasUnanalyzedC; private final Boolean hasUnanalyzedCpp; private final Set<String> customSecurityConfigs; @@ -57,6 +59,7 @@ public class TelemetryData { plugins = builder.plugins; database = builder.database; edition = builder.edition; + defaultQualityGate = builder.defaultQualityGate; installationDate = builder.installationDate; installationVersion = builder.installationVersion; inDocker = builder.inDocker; @@ -64,6 +67,7 @@ public class TelemetryData { users = builder.users; projects = builder.projects; projectStatistics = builder.projectStatistics; + qualityGates = builder.qualityGates; hasUnanalyzedC = builder.hasUnanalyzedC; hasUnanalyzedCpp = builder.hasUnanalyzedCpp; customSecurityConfigs = requireNonNullElse(builder.customSecurityConfigs, Set.of()); @@ -93,6 +97,10 @@ public class TelemetryData { return Optional.ofNullable(edition); } + public String getDefaultQualityGate() { + return defaultQualityGate; + } + public Long getInstallationDate() { return installationDate; } @@ -133,6 +141,10 @@ public class TelemetryData { return projectStatistics; } + public List<QualityGate> getQualityGates() { + return qualityGates; + } + static Builder builder() { return new Builder(); } @@ -144,6 +156,7 @@ public class TelemetryData { private Map<String, String> plugins; private Database database; private Edition edition; + private String defaultQualityGate; private Long installationDate; private String installationVersion; private boolean inDocker = false; @@ -154,6 +167,7 @@ public class TelemetryData { private List<UserTelemetryDto> users; private List<Project> projects; private List<ProjectStatistics> projectStatistics; + private List<QualityGate> qualityGates; private Builder() { // enforce static factory method @@ -189,6 +203,11 @@ public class TelemetryData { return this; } + Builder setDefaultQualityGate(String defaultQualityGate) { + this.defaultQualityGate = defaultQualityGate; + return this; + } + Builder setInstallationDate(@Nullable Long installationDate) { this.installationDate = installationDate; return this; @@ -244,6 +263,11 @@ public class TelemetryData { return this; } + Builder setQualityGates(List<QualityGate> qualityGates) { + this.qualityGates = qualityGates; + return this; + } + private static void requireNonNullValues(Object... values) { Arrays.stream(values).forEach(Objects::requireNonNull); } @@ -256,15 +280,9 @@ public class TelemetryData { record Project(String projectUuid, Long lastAnalysis, String language, Long loc) { } - record ProjectStatistics(String projectUuid, Long branchCount, Long pullRequestCount, String scm, String ci, String devopsPlatform) { - ProjectStatistics(String projectUuid, Long branchCount, Long pullRequestCount, - @Nullable String scm, @Nullable String ci, @Nullable String devopsPlatform) { - this.projectUuid = projectUuid; - this.branchCount = branchCount; - this.pullRequestCount = pullRequestCount; - this.scm = scm; - this.ci = ci; - this.devopsPlatform = devopsPlatform; - } + record ProjectStatistics(String projectUuid, Long branchCount, Long pullRequestCount, String qualityGate, String scm, String ci, String devopsPlatform) { + } + + record QualityGate(String uuid, boolean isCaycCompliant) { } } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java index f5356ac46fa..116612e8b02 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java @@ -56,6 +56,7 @@ public class TelemetryDataJsonWriter { json.prop("messageSequenceNumber", statistics.getMessageSequenceNumber()); json.prop("localTimestamp", toUtc(system2.now())); statistics.getEdition().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH))); + json.prop("defaultQualityGate", statistics.getDefaultQualityGate()); json.name("database"); json.beginObject(); json.prop("name", statistics.getDatabase().name()); @@ -94,6 +95,7 @@ public class TelemetryDataJsonWriter { writeUserData(json, statistics); writeProjectData(json, statistics); writeProjectStatsData(json, statistics); + writeQualityGates(json, statistics); extensions.forEach(e -> e.write(json)); @@ -150,6 +152,7 @@ public class TelemetryDataJsonWriter { json.prop("projectUuid", project.projectUuid()); json.prop("branchCount", project.branchCount()); json.prop("pullRequestCount", project.pullRequestCount()); + json.prop("qualityGate", project.qualityGate()); json.prop("scm", project.scm()); json.prop("ci", project.ci()); json.prop("devopsPlatform", project.devopsPlatform()); @@ -159,6 +162,20 @@ public class TelemetryDataJsonWriter { } } + private static void writeQualityGates(JsonWriter json, TelemetryData statistics) { + if (statistics.getQualityGates() != null) { + json.name("quality-gates"); + json.beginArray(); + statistics.getQualityGates().forEach(qualityGate -> { + json.beginObject(); + json.prop("uuid", qualityGate.uuid()); + json.prop("isCaycCompliant", qualityGate.isCaycCompliant()); + json.endObject(); + }); + json.endArray(); + } + } + @NotNull private static String toUtc(long date) { return DateTimeFormatter.ofPattern(DATETIME_FORMAT) diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java index 1748571360b..d3dc46faa15 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java @@ -68,12 +68,13 @@ public class TelemetryDataJsonWriterTest { TelemetryData data = telemetryBuilder().build(); String json = writeTelemetryData(data); - - assertJson(json).isSimilarTo("{" + - " \"id\": \"" + data.getServerId() + "\"," + - " \"version\": \"" + data.getVersion() + "\"," + - " \"messageSequenceNumber\": " + data.getMessageSequenceNumber() + - "}"); + assertJson(json).isSimilarTo(""" + { + "id": "%s", + "version": "%s", + "messageSequenceNumber": %s + } + """.formatted(data.getServerId(), data.getVersion(), data.getMessageSequenceNumber())); } @Test @@ -93,10 +94,25 @@ public class TelemetryDataJsonWriterTest { .build(); String json = writeTelemetryData(data); + assertJson(json).isSimilarTo(""" + { + "edition": "%s" + } + """.formatted(edition.name().toLowerCase(Locale.ENGLISH))); + } + + @Test + public void writes_default_qg() { + TelemetryData data = telemetryBuilder() + .setDefaultQualityGate("default-qg") + .build(); - assertJson(json).isSimilarTo("{" + - " \"edition\": \"" + edition.name().toLowerCase(Locale.ENGLISH) + "\"" + - "}"); + String json = writeTelemetryData(data); + assertJson(json).isSimilarTo(""" + { + "defaultQualityGate": "%s" + } + """.formatted(data.getDefaultQualityGate())); } @Test @@ -108,13 +124,14 @@ public class TelemetryDataJsonWriterTest { .build(); String json = writeTelemetryData(data); - - assertJson(json).isSimilarTo("{" + - " \"database\": {" + - " \"name\": \"" + name + "\"," + - " \"version\": \"" + version + "\"" + - " }" + - "}"); + assertJson(json).isSimilarTo(""" + { + "database": { + "name": "%s", + "version": "%s" + } + } + """.formatted(name, version)); } @Test @@ -125,9 +142,11 @@ public class TelemetryDataJsonWriterTest { String json = writeTelemetryData(data); - assertJson(json).isSimilarTo("{" + - " \"plugins\": []" + - "}"); + assertJson(json).isSimilarTo(""" + { + "plugins": [] + } + """); } @Test @@ -140,13 +159,11 @@ public class TelemetryDataJsonWriterTest { .build(); String json = writeTelemetryData(data); - - assertJson(json).isSimilarTo("{" + - " \"plugins\": " + - "[" + - plugins.entrySet().stream().map(e -> "{\"name\":\"" + e.getKey() + "\",\"version\":\"" + e.getValue() + "\"}").collect(joining(",")) + - "]" + - "}"); + assertJson(json).isSimilarTo(""" + { + "plugins": [%s] + } + """.formatted(plugins.entrySet().stream().map(e -> "{\"name\":\"" + e.getKey() + "\",\"version\":\"" + e.getValue() + "\"}").collect(joining(",")))); } @Test @@ -168,9 +185,11 @@ public class TelemetryDataJsonWriterTest { String json = writeTelemetryData(data); - assertJson(json).isSimilarTo("{" + - " \"installationDate\":\"1970-01-01T00:00:01+0000\"," + - "}"); + assertJson(json).isSimilarTo(""" + { + "installationDate":"1970-01-01T00:00:01+0000" + } + """); } @Test @@ -192,10 +211,11 @@ public class TelemetryDataJsonWriterTest { .build(); String json = writeTelemetryData(data); - - assertJson(json).isSimilarTo("{" + - " \"installationVersion\":\"" + installationVersion + "\"" + - "}"); + assertJson(json).isSimilarTo(""" + { + "installationVersion": "%s" + } + """.formatted(installationVersion)); } @Test @@ -206,10 +226,11 @@ public class TelemetryDataJsonWriterTest { .build(); String json = writeTelemetryData(data); - - assertJson(json).isSimilarTo("{" + - " \"docker\":" + isInDocker + - "}"); + assertJson(json).isSimilarTo(""" + { + "docker": %s + } + """.formatted(isInDocker)); } @Test @@ -249,9 +270,11 @@ public class TelemetryDataJsonWriterTest { String json = writeTelemetryData(data); - assertJson(json).isSimilarTo("{" + - " \"customSecurityConfig\": [\"php\", \"java\"]" + - "}"); + assertJson(json).isSimilarTo(""" + { + "customSecurityConfig": ["php", "java"] + } + """); } @Test @@ -261,9 +284,11 @@ public class TelemetryDataJsonWriterTest { TelemetryData data = telemetryBuilder().build(); String json = writeTelemetryData(data); - assertJson(json).isSimilarTo("{" + - " \"localTimestamp\": \"1970-01-01T00:00:01+0000\"" + - "}"); + assertJson(json).isSimilarTo(""" + { + "localTimestamp": "1970-01-01T00:00:01+0000" + } + """); } @Test @@ -274,31 +299,34 @@ public class TelemetryDataJsonWriterTest { String json = writeTelemetryData(data); - assertJson(json).isSimilarTo("{" + - " \"users\": [" + - " {" + - " \"userUuid\":\"" + DigestUtils.sha3_224Hex("uuid-0") + "\"," + - " \"lastActivity\":\"1970-01-01T00:00:00+0000\"," + - " \"identityProvider\":\"gitlab\"," + - " \"lastSonarlintActivity\":\"1970-01-01T00:00:00+0000\"," + - " \"status\":\"active\"" + - " }," + - " {" + - " \"userUuid\":\"" + DigestUtils.sha3_224Hex("uuid-1") + "\"," + - " \"lastActivity\":\"1970-01-01T00:00:00+0000\"," + - " \"identityProvider\":\"gitlab\"," + - " \"lastSonarlintActivity\":\"1970-01-01T00:00:00+0000\"," + - " \"status\":\"inactive\"" + - " }," + - " {" + - " \"userUuid\":\"" + DigestUtils.sha3_224Hex("uuid-2") + "\"," + - " \"lastActivity\":\"1970-01-01T00:00:00+0000\"," + - " \"identityProvider\":\"gitlab\"," + - " \"lastSonarlintActivity\":\"1970-01-01T00:00:00+0000\"," + - " \"status\":\"active\"" + - " }" + - " ]" + - "}"); + assertJson(json).isSimilarTo(""" + { + "users": [ + { + "userUuid": "%s", + "status": "active", + "identityProvider": "gitlab", + "lastActivity": "1970-01-01T00:00:00+0000", + "lastSonarlintActivity": "1970-01-01T00:00:00+0000" + }, + { + "userUuid": "%s", + "status": "inactive", + "identityProvider": "gitlab", + "lastActivity": "1970-01-01T00:00:00+0000", + "lastSonarlintActivity": "1970-01-01T00:00:00+0000" + }, + { + "userUuid": "%s", + "status": "active", + "identityProvider": "gitlab", + "lastActivity": "1970-01-01T00:00:00+0000", + "lastSonarlintActivity": "1970-01-01T00:00:00+0000" + } + ] + } + """ + .formatted(DigestUtils.sha3_224Hex("uuid-0"), DigestUtils.sha3_224Hex("uuid-1"), DigestUtils.sha3_224Hex("uuid-2"))); } @Test @@ -309,28 +337,30 @@ public class TelemetryDataJsonWriterTest { String json = writeTelemetryData(data); - assertJson(json).isSimilarTo("{" + - " \"projects\": [" + - " {" + - " \"projectUuid\": \"uuid-0\"," + - " \"lastAnalysis\":\"1970-01-01T00:00:00+0000\"," + - " \"language\": \"lang-0\"," + - " \"loc\": 2" + - " }," + - " {" + - " \"projectUuid\": \"uuid-1\"," + - " \"lastAnalysis\":\"1970-01-01T00:00:00+0000\"," + - " \"language\": \"lang-1\"," + - " \"loc\": 4" + - " }," + - " {" + - " \"projectUuid\": \"uuid-2\"," + - " \"lastAnalysis\":\"1970-01-01T00:00:00+0000\"," + - " \"language\": \"lang-2\"," + - " \"loc\": 6" + - " }" + - " ]" + - "}"); + assertJson(json).isSimilarTo(""" + { + "projects": [ + { + "projectUuid": "uuid-0", + "lastAnalysis": "1970-01-01T00:00:00+0000", + "language": "lang-0", + "loc": 2 + }, + { + "projectUuid": "uuid-1", + "lastAnalysis": "1970-01-01T00:00:00+0000", + "language": "lang-1", + "loc": 4 + }, + { + "projectUuid": "uuid-2", + "lastAnalysis": "1970-01-01T00:00:00+0000", + "language": "lang-2", + "loc": 6 + } + ] + } + """); } @Test @@ -348,6 +378,7 @@ public class TelemetryDataJsonWriterTest { "projectUuid": "uuid-0", "branchCount": 2, "pullRequestCount": 2, + "qualityGate": "qg-0" "scm": "scm-0", "ci": "ci-0", "devopsPlatform": "devops-0" @@ -356,6 +387,7 @@ public class TelemetryDataJsonWriterTest { "projectUuid": "uuid-1", "branchCount": 4, "pullRequestCount": 4, + "qualityGate": "qg-1" "scm": "scm-1", "ci": "ci-1", "devopsPlatform": "devops-1" @@ -364,6 +396,7 @@ public class TelemetryDataJsonWriterTest { "projectUuid": "uuid-2", "branchCount": 6, "pullRequestCount": 6, + "qualityGate": "qg-2" "scm": "scm-2", "ci": "ci-2", "devopsPlatform": "devops-2" @@ -384,6 +417,34 @@ public class TelemetryDataJsonWriterTest { assertThat(json).doesNotContain("hasUnanalyzedC", "hasUnanalyzedCpp"); } + @Test + public void writes_all_quality_gates() { + TelemetryData data = telemetryBuilder() + .setQualityGates(attachQualityGates()) + .build(); + + String json = writeTelemetryData(data); + assertJson(json).isSimilarTo(""" + { + "quality-gates": [ + { + "uuid": "uuid-0", + "isCaycCompliant": true + }, + { + "uuid": "uuid-1", + "isCaycCompliant": false + }, + { + "uuid": "uuid-2", + "isCaycCompliant": true + } + ] + } + """ + ); + } + private static TelemetryData.Builder telemetryBuilder() { return TelemetryData.builder() .setServerId("foo") @@ -406,7 +467,12 @@ public class TelemetryDataJsonWriterTest { } private List<TelemetryData.ProjectStatistics> attachProjectStats() { - return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.ProjectStatistics("uuid-" + i, (i + 1L) * 2L, (i + 1L) * 2L, "scm-" + i, "ci-" + i, "devops-" + i)) + return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.ProjectStatistics("uuid-" + i, (i + 1L) * 2L, (i + 1L) * 2L, "qg-" + i, "scm-" + i, "ci-" + i, "devops-" + i)) + .collect(Collectors.toList()); + } + + private List<TelemetryData.QualityGate> attachQualityGates() { + return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.QualityGate("uuid-" + i, i % 2 == 0)) .collect(Collectors.toList()); } diff --git a/server/sonar-webserver-core/build.gradle b/server/sonar-webserver-core/build.gradle index e4fc8952baa..1abb12d00a5 100644 --- a/server/sonar-webserver-core/build.gradle +++ b/server/sonar-webserver-core/build.gradle @@ -50,6 +50,7 @@ dependencies { api project(':sonar-markdown') api project(':sonar-plugin-api-impl') api project(':sonar-ws') + implementation project(path: ':server:sonar-webserver-webapi') compileOnlyApi 'com.google.code.findbugs:jsr305' // not a transitive dep. At runtime lib/jdbc/h2 is used diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java index 3fee8bf14e0..9e79bb1dabf 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java @@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; @@ -44,8 +45,12 @@ import org.sonar.db.alm.setting.ProjectAlmKeyAndProject; import org.sonar.db.component.AnalysisPropertyValuePerProject; import org.sonar.db.component.PrBranchAnalyzedLanguageCountByProjectDto; import org.sonar.db.measure.ProjectMeasureDto; +import org.sonar.db.qualitygate.ProjectQgateAssociationDto; +import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.server.platform.DockerSupport; import org.sonar.server.property.InternalProperties; +import org.sonar.server.qualitygate.QualityGateCaycChecker; +import org.sonar.server.qualitygate.QualityGateFinder; import org.sonar.server.telemetry.TelemetryData.Database; import static java.util.Arrays.asList; @@ -80,11 +85,13 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { private final Configuration configuration; private final InternalProperties internalProperties; private final DockerSupport dockerSupport; + private final QualityGateCaycChecker qualityGateCaycChecker; + private final QualityGateFinder qualityGateFinder; @Inject public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository, PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration, - DockerSupport dockerSupport) { + DockerSupport dockerSupport, QualityGateCaycChecker qualityGateCaycChecker, QualityGateFinder qualityGateFinder) { this.server = server; this.dbClient = dbClient; this.pluginRepository = pluginRepository; @@ -92,6 +99,8 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { this.internalProperties = internalProperties; this.configuration = configuration; this.dockerSupport = dockerSupport; + this.qualityGateCaycChecker = qualityGateCaycChecker; + this.qualityGateFinder = qualityGateFinder; } private static Database loadDatabaseMetadata(DbSession dbSession) { @@ -117,9 +126,13 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { try (DbSession dbSession = dbClient.openSession(false)) { data.setDatabase(loadDatabaseMetadata(dbSession)); + String defaultQualityGateUuid = qualityGateFinder.getDefault(dbSession).getUuid(); + + data.setDefaultQualityGate(defaultQualityGateUuid); resolveUnanalyzedLanguageCode(data, dbSession); - resolveProjectStatistics(data, dbSession); + resolveProjectStatistics(data, dbSession, defaultQualityGateUuid); resolveProjects(data, dbSession); + resolveQualityGates(data, dbSession); resolveUsers(data, dbSession); } @@ -151,7 +164,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { return internalProperties.read(I_PROP_MESSAGE_SEQUENCE).map(Long::parseLong).orElse(0L); } - private void resolveProjectStatistics(TelemetryData.Builder data, DbSession dbSession) { + private void resolveProjectStatistics(TelemetryData.Builder data, DbSession dbSession, String defaultQualityGateUuid) { List<String> projectUuids = dbClient.projectDao().selectAllProjectUuids(dbSession); Map<String, String> scmByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDSCM); Map<String, String> ciByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDCI); @@ -159,12 +172,16 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { Map<String, PrBranchAnalyzedLanguageCountByProjectDto> prAndBranchCountByProjects = dbClient.branchDao().countPrBranchAnalyzedLanguageByProjectUuid(dbSession) .stream().collect(Collectors.toMap(PrBranchAnalyzedLanguageCountByProjectDto::getProjectUuid, Function.identity())); + + Map<String, String> projectQgatesMap = getProjectQgatesMap(dbSession); + List<TelemetryData.ProjectStatistics> projectStatistics = new ArrayList<>(); for (String projectUuid : projectUuids) { Optional<PrBranchAnalyzedLanguageCountByProjectDto> counts = ofNullable(prAndBranchCountByProjects.get(projectUuid)); Long branchCount = counts.map(PrBranchAnalyzedLanguageCountByProjectDto::getBranch).orElse(0L); Long pullRequestCount = counts.map(PrBranchAnalyzedLanguageCountByProjectDto::getPullRequest).orElse(0L); + String qualityGate = projectQgatesMap.getOrDefault(projectUuid, defaultQualityGateUuid); String scm = Optional.ofNullable(scmByProject.get(projectUuid)).orElse(UNDETECTED); String ci = Optional.ofNullable(ciByProject.get(projectUuid)).orElse(UNDETECTED); String devopsPlatform = null; @@ -175,7 +192,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { devopsPlatform = Optional.ofNullable(devopsPlatform).orElse(UNDETECTED); projectStatistics.add( - new TelemetryData.ProjectStatistics(projectUuid, branchCount, pullRequestCount, scm, ci, devopsPlatform)); + new TelemetryData.ProjectStatistics(projectUuid, branchCount, pullRequestCount, qualityGate, scm, ci, devopsPlatform)); } data.setProjectStatistics(projectStatistics); } @@ -194,6 +211,18 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { data.setProjects(projects); } + private void resolveQualityGates(TelemetryData.Builder data, DbSession dbSession) { + List<TelemetryData.QualityGate> qualityGates = new ArrayList<>(); + Collection<QualityGateDto> qualityGateDtos = dbClient.qualityGateDao().selectAll(dbSession); + for (QualityGateDto qualityGateDto : qualityGateDtos) { + qualityGates.add( + new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGateDto.getUuid())) + ); + } + + data.setQualityGates(qualityGates); + } + private void resolveUsers(TelemetryData.Builder data, DbSession dbSession) { data.setUsers(dbClient.userDao().selectUsersForTelemetry(dbSession)); } @@ -236,6 +265,12 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { return alm + "_server"; } + private Map<String, String> getProjectQgatesMap(DbSession dbSession) { + return dbClient.projectQgateAssociationDao().selectAll(dbSession) + .stream() + .collect(Collectors.toMap(ProjectQgateAssociationDto::getUuid, p -> Optional.ofNullable(p.getGateUuid()).orElse(""))); + } + private static boolean checkIfCloudAlm(String almRaw, String alm, String url, String cloudUrl) { return alm.equals(almRaw) && startsWithIgnoreCase(url, cloudUrl); } diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java index 92c5bb10da6..16f742cfa19 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java @@ -31,6 +31,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,12 +47,15 @@ import org.sonar.db.component.AnalysisPropertyDto; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; import org.sonar.db.metric.MetricDto; +import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.db.user.UserDbTester; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTelemetryDto; import org.sonar.server.platform.DockerSupport; import org.sonar.server.property.InternalProperties; import org.sonar.server.property.MapInternalProperties; +import org.sonar.server.qualitygate.QualityGateCaycChecker; +import org.sonar.server.qualitygate.QualityGateFinder; import org.sonar.updatecenter.common.Version; import static java.util.Arrays.asList; @@ -89,12 +93,23 @@ public class TelemetryDataLoaderImplTest { private final Configuration configuration = mock(Configuration.class); private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); private final DockerSupport dockerSupport = mock(DockerSupport.class); + private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class); + private final QualityGateFinder qualityGateFinder = new QualityGateFinder(db.getDbClient()); private final InternalProperties internalProperties = spy(new MapInternalProperties()); private final TelemetryDataLoader communityUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider, - internalProperties, configuration, dockerSupport); + internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder); private final TelemetryDataLoader commercialUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider, - internalProperties, configuration, dockerSupport); + internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder); + + private QualityGateDto builtInDefaultQualityGate; + + @Before + public void setUpBuiltInQualityGate() { + String builtInQgName = "Sonar Way"; + builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true)); + db.qualityGates().setDefaultQualityGate(builtInDefaultQualityGate); + } @Test public void send_telemetry_data() { @@ -151,10 +166,18 @@ public class TelemetryDataLoaderImplTest { db.almSettings().insertAzureProjectAlmSetting(almSettingDto, db.components().getProjectDto(project1)); db.almSettings().insertGitlabProjectAlmSetting(gitHubAlmSetting, db.components().getProjectDto(project2)); + // quality gates + QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG1").setBuiltIn(true)); + QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG2")); + + // link one project to a non-default QG + db.qualityGates().associateProjectToQualityGate(db.components().getProjectDto(project1), qualityGate1); + TelemetryData data = communityUnderTest.load(); assertThat(data.getServerId()).isEqualTo(serverId); assertThat(data.getVersion()).isEqualTo(version); assertThat(data.getEdition()).contains(DEVELOPER); + assertThat(data.getDefaultQualityGate()).isEqualTo(builtInDefaultQualityGate.getUuid()); assertThat(data.getMessageSequenceNumber()).isOne(); assertDatabaseMetadata(data.getDatabase()); assertThat(data.getPlugins()).containsOnly( @@ -177,11 +200,18 @@ public class TelemetryDataLoaderImplTest { tuple(project2.uuid(), "java", 180L, analysisDate), tuple(project2.uuid(), "js", 20L, analysisDate)); assertThat(data.getProjectStatistics()) - .extracting(TelemetryData.ProjectStatistics::branchCount, TelemetryData.ProjectStatistics::pullRequestCount, + .extracting(TelemetryData.ProjectStatistics::branchCount, TelemetryData.ProjectStatistics::pullRequestCount, TelemetryData.ProjectStatistics::qualityGate, TelemetryData.ProjectStatistics::scm, TelemetryData.ProjectStatistics::ci, TelemetryData.ProjectStatistics::devopsPlatform) .containsExactlyInAnyOrder( - tuple(1L, 0L, "scm-1", "ci-1", "azure_devops_cloud"), - tuple(1L, 0L, "scm-2", "ci-2", "github_cloud")); + tuple(1L, 0L, qualityGate1.getUuid(), "scm-1", "ci-1", "azure_devops_cloud"), + tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud")); + assertThat(data.getQualityGates()) + .extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::isCaycCompliant) + .containsExactlyInAnyOrder( + tuple(builtInDefaultQualityGate.getUuid(), false), + tuple(qualityGate1.getUuid(), false), + tuple(qualityGate2.getUuid(), false) + ); } private List<UserDto> composeActiveUsers(int count) { @@ -393,6 +423,17 @@ public class TelemetryDataLoaderImplTest { assertThat(data.isScimEnabled()).isEqualTo(isEnabled); } + @Test + public void default_quality_gate_sent_with_project() { + db.components().insertPublicProject(); + QualityGateDto qualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("anything").setBuiltIn(true)); + db.qualityGates().setDefaultQualityGate(qualityGate); + TelemetryData data = communityUnderTest.load(); + assertThat(data.getProjectStatistics()) + .extracting(TelemetryData.ProjectStatistics::qualityGate) + .containsOnly(qualityGate.getUuid()); + } + private PluginInfo newPlugin(String key, String version) { return new PluginInfo(key) .setVersion(Version.create(version)); |