]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18188 Share CAYC quality gate info via telemetry
authorAlain Kermis <alain.kermis@sonarsource.com>
Thu, 12 Jan 2023 13:19:38 +0000 (14:19 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 13 Jan 2023 20:02:46 +0000 (20:02 +0000)
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java
server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java
server/sonar-webserver-core/build.gradle
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java

index 4e2bcd598a0d18313ec210fdbdece7dc9fbf774c..058a80af47d96479a1ed31f7836a2bcfb27f46ad 100644 (file)
@@ -81,6 +81,7 @@ public final class SqTables {
     "project_mappings",
     "project_measures",
     "project_qprofiles",
+    "project_qgates",
     "properties",
     "push_events",
     "qprofile_changes",
index 086d1025a45b0b4d836526670c0e7515ac8b9023..205c36d64af13d501a0553a61960ab76444d7921 100644 (file)
@@ -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)
index 97bfdebf66c973bf8432528c2b53c2c1c6737b28..931247f3bde0720d0120d7e9839b7172bc425766 100644 (file)
@@ -27,6 +27,8 @@ public interface ProjectQgateAssociationMapper {
 
   List<ProjectQgateAssociationDto> selectProjects(@Param("query") ProjectQgateAssociationQuery query);
 
+  List<ProjectQgateAssociationDto> selectAll();
+
   @CheckForNull
   String selectQGateUuidByProjectUuid(String projectUuid);
 
index 4419b062141c7dfc4bd87e3081d6ae75a2d33225..b71d11746b7fb1eff21778580a5f19f731de18de 100644 (file)
     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
index d57404aff02b7883dfc4fea81e547406a8f16294..d03b6c59e3a6447a4c4ff27fa3ecf839308921f5 100644 (file)
@@ -145,6 +145,35 @@ public class ProjectQgateAssociationDaoTest {
       .containsExactly(project1.uuid(), project3.uuid(), project2.uuid());
   }
 
+  @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();
index 61f4dc47e43f8bc014a994f5bc78fe93a6663e78..6daa1256f8a5cf1afbf8ebd199cac1210c01b52a 100644 (file)
@@ -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) {
   }
 }
index f5356ac46fa94d61bc10ee9c9327aa1983db56db..116612e8b02fa236e6eb7690a17eaaf4ec620878 100644 (file)
@@ -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)
index 1748571360bbcd6c833082f4beeac907497f08f1..d3dc46faa15f9cf053a2b2b3b4f597816c197714 100644 (file)
@@ -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());
   }
 
index e4fc8952baa3129b5b61fb3574740380f6e34dee..1abb12d00a51b64de559cd1bb7ecf9b7eeb404ff 100644 (file)
@@ -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
index 3fee8bf14e0265c30c7c09e5bf0e420608d46c1d..9e79bb1dabf6852f53b7eeb3561d2c82485b72ef 100644 (file)
@@ -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);
   }
index 92c5bb10da6ae58b6dcaf8cdade234201ea540a3..16f742cfa1978bca38e6abb6fe0ffcff39d3318d 100644 (file)
@@ -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));