]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20359 add quality profile to projects in telemetry
authorBenjamin Campomenosi <109955405+benjamin-campomenosi-sonarsource@users.noreply.github.com>
Fri, 15 Sep 2023 14:20:23 +0000 (16:20 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 15 Sep 2023 20:03:06 +0000 (20:03 +0000)
server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/QualityProfileDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ProjectQProfileLanguageAssociationDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml
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/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java

index 52591e688d4ba31a77f49dd672ebbffb4b69615c..34674beab940f6a74eba73f49f92b44efabf32ee 100644 (file)
@@ -817,11 +817,10 @@ public class QualityProfileDaoIT {
     QProfileDto profile3 = newQualityProfileDto();
 
     assertThat(underTest.selectAllProjectAssociations(dbSession))
-      .extracting("projectUuid", "projectKey", "projectName", "profileKey")
+      .extracting("projectUuid", "profileKey", "language")
       .containsExactlyInAnyOrder(
-        tuple(project1.getUuid(), project1.getKey(), project1.getName(), profile1.getKee()),
-        tuple(project2.getUuid(), project2.getKey(), project2.getName(), profile2.getKee())
-      );
+        tuple(project1.getUuid(), profile1.getKee(), profile1.getLanguage()),
+        tuple(project2.getUuid(), profile2.getKee(), profile2.getLanguage()));
   }
 
   @Test
@@ -842,8 +841,7 @@ public class QualityProfileDaoIT {
       .extracting("kee", "name", "language")
       .containsExactlyInAnyOrder(
         tuple(defaultProfile1.getKee(), defaultProfile1.getName(), defaultProfile1.getLanguage()),
-        tuple(defaultProfile2.getKee(), defaultProfile2.getName(), defaultProfile2.getLanguage())
-      );
+        tuple(defaultProfile2.getKee(), defaultProfile2.getName(), defaultProfile2.getLanguage()));
   }
 
   @Test
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ProjectQProfileLanguageAssociationDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ProjectQProfileLanguageAssociationDto.java
new file mode 100644 (file)
index 0000000..413f704
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+public record ProjectQProfileLanguageAssociationDto(String projectUuid, String profileKey, String language) {
+}
index b392a9b8de56e65f60c966ce5ac12cee3a596276..7a57fad23eee4c5a9bd6547dd54ba6dbd57838fd 100644 (file)
@@ -248,7 +248,7 @@ public class QualityProfileDao implements Dao {
     return mapper(dbSession).selectProjectAssociations(profile.getKee(), nameQuery);
   }
 
-  public List<ProjectQprofileAssociationDto> selectAllProjectAssociations(DbSession dbSession) {
+  public List<ProjectQProfileLanguageAssociationDto> selectAllProjectAssociations(DbSession dbSession) {
     return mapper(dbSession).selectAllProjectAssociations();
   }
   public Collection<String> selectUuidsOfCustomRulesProfiles(DbSession dbSession, String language, String name) {
index 0fe72c5c5956cb45fab17427ac9fac272b782d6d..5c20434255c4e0afc068a035368c10bff91830f0 100644 (file)
@@ -109,6 +109,7 @@ public interface QualityProfileMapper {
     @Param("oldProfileUuid") String oldProfileUuid);
 
   void deleteProjectProfileAssociation(@Param("projectUuid") String projectUuid, @Param("profileUuid") String profileUuid);
+
   void deleteProjectAssociationByProfileUuids(@Param("profileUuids") Collection<String> profileUuids);
 
   List<ProjectQprofileAssociationDto> selectSelectedProjects(
@@ -123,7 +124,7 @@ public interface QualityProfileMapper {
     @Param("profileUuid") String profileUuid,
     @Param("nameOrKeyQuery") String nameOrKeyQuery);
 
-  List<ProjectQprofileAssociationDto> selectAllProjectAssociations();
+  List<ProjectQProfileLanguageAssociationDto> selectAllProjectAssociations();
 
   List<String> selectUuidsOfCustomRuleProfiles(@Param("language") String language, @Param("name") String name);
 
index 3a461b608cbfe2d0cbc05d1b50819d0edb71beb6..6712eaf50031dd4b32f197d7056cc8b2ac6be590 100644 (file)
     ORDER BY pj.name ASC
   </select>
 
-    <select id="selectAllProjectAssociations" resultType="org.sonar.db.qualityprofile.ProjectQprofileAssociationDto">
-    SELECT
-      pj.uuid as projectUuid,
-      pj.kee as projectKey,
-      pj.name as projectName,
-      pp.profile_key as profileKey
-    FROM projects pj
-    INNER JOIN project_qprofiles pp ON pp.project_uuid = pj.uuid
-    WHERE
-      pj.qualifier='TRK'
+    <select id="selectAllProjectAssociations" resultType="org.sonar.db.qualityprofile.ProjectQProfileLanguageAssociationDto">
+      SELECT
+        pj.uuid        as projectUuid,
+        pp.profile_key as profileKey,
+        rp.language    as language
+      FROM projects pj
+        INNER JOIN project_qprofiles pp ON pp.project_uuid = pj.uuid
+        INNER JOIN org_qprofiles oq ON pp.profile_key = oq.uuid
+        INNER JOIN rules_profiles rp on oq.rules_profile_uuid = rp.uuid
+      WHERE pj.qualifier = 'TRK'
     </select>
 
   <select id="selectUuidsOfCustomRuleProfiles" parameterType="map" resultType="string">
index 818d0c11ea4bd7c06a1ece41fdc13828094578e6..ee42c111c68bf927a344698b495a78c6dc5029f8 100644 (file)
@@ -356,7 +356,7 @@ public class TelemetryData {
   record Branch(String projectUuid, String branchUuid, int ncdId, int greenQualityGateCount, int analysisCount, boolean excludeFromPurge) {
   }
 
-  record Project(String projectUuid, Long lastAnalysis, String language, Long loc) {
+  record Project(String projectUuid, Long lastAnalysis, String language, String qualityProfile, Long loc) {
   }
 
   record QualityGate(String uuid, String caycStatus) {
index 357ec98ac3a063f38c3d7da54081702a206d1124..3e61c0036c01c75b67f9a3bdc8e9903d012f5cc0 100644 (file)
@@ -146,6 +146,7 @@ public class TelemetryDataJsonWriter {
         }
         json.prop(LANGUAGE_PROPERTY, project.language());
         json.prop("loc", project.loc());
+        json.prop("qualityProfile", project.qualityProfile());
         json.endObject();
       });
       json.endArray();
index 120d8ca295c5959aabf974b8c54d27e7d133fb0b..ad47094834d09b94ba581a33f13769962ad5c93f 100644 (file)
@@ -400,18 +400,21 @@ public class TelemetryDataJsonWriterTest {
             "projectUuid": "uuid-0",
             "lastAnalysis": "1970-01-01T00:00:00+0000",
             "language": "lang-0",
+            "qualityProfile" : "qprofile-0",
             "loc": 2
           },
           {
             "projectUuid": "uuid-1",
             "lastAnalysis": "1970-01-01T00:00:00+0000",
             "language": "lang-1",
+            "qualityProfile" : "qprofile-1",
             "loc": 4
           },
           {
             "projectUuid": "uuid-2",
             "lastAnalysis": "1970-01-01T00:00:00+0000",
             "language": "lang-2",
+            "qualityProfile" : "qprofile-2",
             "loc": 6
           }
         ]
@@ -656,7 +659,7 @@ public class TelemetryDataJsonWriterTest {
   }
 
   private static List<TelemetryData.Project> attachProjects() {
-    return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, (i + 1L) * 2L)).toList();
+    return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, "qprofile-" + i, (i + 1L) * 2L)).toList();
   }
 
   private static List<TelemetryData.ProjectStatistics> attachProjectStatsWithMetrics() {
index 6382bef3174767c96ee65620c953f9707f03efa5..b10220642d8d7f96fd0d3223132d945c3cec5562 100644 (file)
@@ -116,6 +116,8 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   private final Set<NewCodeDefinition> newCodeDefinitions = new HashSet<>();
   private final Map<String, NewCodeDefinition> ncdByProject = new HashMap<>();
   private final Map<String, NewCodeDefinition> ncdByBranch = new HashMap<>();
+  private final Map<String, String> defaultQualityProfileByLanguage = new HashMap<>();
+  private final Map<ProjectLanguageKey, String> qualityProfileByProjectAndLanguage = new HashMap<>();
   private NewCodeDefinition instanceNcd = NewCodeDefinition.getInstanceDefault();
 
   @Inject
@@ -160,6 +162,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     try (DbSession dbSession = dbClient.openSession(false)) {
       var branchMeasuresDtos = dbClient.branchDao().selectBranchMeasuresWithCaycMetric(dbSession);
       loadNewCodeDefinitions(dbSession, branchMeasuresDtos);
+      loadQualityProfiles(dbSession);
 
       data.setDatabase(loadDatabaseMetadata(dbSession));
       data.setNcdId(instanceNcd.hashCode());
@@ -211,6 +214,8 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     this.ncdByBranch.clear();
     this.ncdByProject.clear();
     this.instanceNcd = NewCodeDefinition.getInstanceDefault();
+    this.defaultQualityProfileByLanguage.clear();
+    this.qualityProfileByProjectAndLanguage.clear();
   }
 
   private void loadNewCodeDefinitions(DbSession dbSession, List<BranchMeasuresDto> branchMeasuresDtos) {
@@ -245,6 +250,16 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     }
   }
 
+  private void loadQualityProfiles(DbSession dbSession) {
+    dbClient.qualityProfileDao().selectAllDefaultProfiles(dbSession)
+      .forEach(defaultQualityProfile -> this.defaultQualityProfileByLanguage.put(defaultQualityProfile.getLanguage(), defaultQualityProfile.getKee()));
+
+    dbClient.qualityProfileDao().selectAllProjectAssociations(dbSession)
+      .forEach(projectAssociation -> qualityProfileByProjectAndLanguage.put(
+        new ProjectLanguageKey(projectAssociation.projectUuid(), projectAssociation.language()),
+        projectAssociation.profileKey()));
+  }
+
   private boolean isCommunityEdition() {
     var edition = editionProvider.get();
     return edition.isPresent() && edition.get() == COMMUNITY;
@@ -274,13 +289,12 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     Map<String, String> scmByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDSCM);
     Map<String, String> ciByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDCI);
     Map<String, ProjectAlmKeyAndProject> almAndUrlByProject = getAlmAndUrlByProject(dbSession);
-    Map<String, PrBranchAnalyzedLanguageCountByProjectDto> prAndBranchCountByProject =
-      dbClient.branchDao().countPrBranchAnalyzedLanguageByProjectUuid(dbSession)
-        .stream().collect(toMap(PrBranchAnalyzedLanguageCountByProjectDto::getProjectUuid, Function.identity()));
+    Map<String, PrBranchAnalyzedLanguageCountByProjectDto> prAndBranchCountByProject = dbClient.branchDao().countPrBranchAnalyzedLanguageByProjectUuid(dbSession)
+      .stream().collect(toMap(PrBranchAnalyzedLanguageCountByProjectDto::getProjectUuid, Function.identity()));
     Map<String, String> qgatesByProject = getProjectQgatesMap(dbSession);
-    Map<String, Map<String, Number>> metricsByProject =
-      getProjectMetricsByMetricKeys(dbSession, TECHNICAL_DEBT_KEY, DEVELOPMENT_COST_KEY, SECURITY_HOTSPOTS_KEY, VULNERABILITIES_KEY,
-        BUGS_KEY);
+    Map<String, Map<String, Number>> metricsByProject = getProjectMetricsByMetricKeys(dbSession, TECHNICAL_DEBT_KEY, DEVELOPMENT_COST_KEY, SECURITY_HOTSPOTS_KEY,
+      VULNERABILITIES_KEY,
+      BUGS_KEY);
     Map<String, Long> securityReportExportedAtByProjectUuid = getSecurityReportExportedAtDateByProjectUuid(dbSession);
 
     List<TelemetryData.ProjectStatistics> projectStatistics = new ArrayList<>();
@@ -336,7 +350,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     data.setProjects(buildProjectsList(branchesWithLargestNcloc, latestSnapshotMap));
   }
 
-  private static List<TelemetryData.Project> buildProjectsList(List<ProjectLocDistributionDto> branchesWithLargestNcloc,
+  private List<TelemetryData.Project> buildProjectsList(List<ProjectLocDistributionDto> branchesWithLargestNcloc,
     Map<String, Long> latestSnapshotMap) {
     return branchesWithLargestNcloc.stream()
       .flatMap(measure -> Arrays.stream(measure.locDistribution().split(";"))
@@ -345,9 +359,17 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
           measure.projectUuid(),
           latestSnapshotMap.get(measure.branchUuid()),
           languageAndLoc[0],
-          Long.parseLong(languageAndLoc[1])
-        ))
-      ).toList();
+          getQualityProfile(measure.projectUuid(), languageAndLoc[0]),
+          Long.parseLong(languageAndLoc[1]))))
+      .toList();
+  }
+
+  private String getQualityProfile(String projectUuid, String language) {
+    String qualityProfile = this.qualityProfileByProjectAndLanguage.get(new ProjectLanguageKey(projectUuid, language));
+    if (qualityProfile != null) {
+      return qualityProfile;
+    }
+    return this.defaultQualityProfileByLanguage.get(language);
   }
 
   private Map<String, String> getNclocMetricUuidMap(DbSession dbSession) {
@@ -362,15 +384,12 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     for (QualityGateDto qualityGateDto : qualityGateDtos) {
       qualityGates.add(
         new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession,
-          qualityGateDto.getUuid()).toString())
-      );
+          qualityGateDto.getUuid()).toString()));
     }
 
     data.setQualityGates(qualityGates);
   }
 
-
-
   private void resolveUsers(TelemetryData.Builder data, DbSession dbSession) {
     data.setUsers(dbClient.userDao().selectUsersForTelemetry(dbSession));
   }
@@ -465,4 +484,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   private TelemetryData.CloudUsage buildCloudUsage() {
     return cloudUsageDataProvider.getCloudUsage();
   }
+
+  private record ProjectLanguageKey(String projectKey, String language) {
+  }
 }
index edf532d205eda7fa3ce26791c3297699238ee4ef..d7494fa47cbfe13869c23d508e78e7d33c473db4 100644 (file)
@@ -219,6 +219,12 @@ public class TelemetryDataLoaderImplTest {
     QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG2"));
 
     //quality profiles
+    QProfileDto javaQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("java"));
+    QProfileDto kotlinQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("kotlin"));
+    QProfileDto jsQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("js"));
+    db.qualityProfiles().associateWithProject(projectData1.getProjectDto(), javaQP, kotlinQP, jsQP);
+    db.qualityProfiles().associateWithProject(projectData2.getProjectDto(), javaQP, jsQP);
+
     QProfileDto qualityProfile1 = db.qualityProfiles().insert(qp -> qp.setIsBuiltIn(true));
     QProfileDto qualityProfile2 = db.qualityProfiles().insert();
     db.qualityProfiles().setAsDefault(qualityProfile1, qualityProfile2);
@@ -296,7 +302,13 @@ public class TelemetryDataLoaderImplTest {
 
     assertThat(data.getQualityProfiles())
       .extracting(TelemetryData.QualityProfile::uuid, TelemetryData.QualityProfile::isBuiltIn)
-      .containsExactlyInAnyOrder(tuple(qualityProfile1.getKee(), qualityProfile1.isBuiltIn()), tuple(qualityProfile2.getKee(), qualityProfile2.isBuiltIn()));
+      .containsExactlyInAnyOrder(
+        tuple(qualityProfile1.getKee(), qualityProfile1.isBuiltIn()),
+        tuple(qualityProfile2.getKee(), qualityProfile2.isBuiltIn()),
+        tuple(jsQP.getKee(), jsQP.isBuiltIn()),
+        tuple(javaQP.getKee(), javaQP.isBuiltIn()),
+        tuple(kotlinQP.getKee(), kotlinQP.isBuiltIn())
+        );
 
   }
 
@@ -372,6 +384,12 @@ public class TelemetryDataLoaderImplTest {
     MetricDto nclocDistrib = db.measures().insertMetric(m -> m.setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY));
 
     ProjectData projectData = db.components().insertPublicProject();
+
+    QProfileDto javaQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("java"));
+    QProfileDto kotlinQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("kotlin"));
+    QProfileDto jsQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("js"));
+    db.qualityProfiles().associateWithProject(projectData.getProjectDto(), javaQP, kotlinQP, jsQP);
+
     ComponentDto mainBranch = projectData.getMainBranchComponent();
     db.measures().insertLiveMeasure(mainBranch, lines, m -> m.setValue(110d));
     db.measures().insertLiveMeasure(mainBranch, ncloc, m -> m.setValue(110d));
@@ -403,6 +421,45 @@ public class TelemetryDataLoaderImplTest {
         tuple(2L, 0L, "undetected", "undetected"));
   }
 
+  @Test
+  public void load_shouldProvideQualityProfileInProjectSection() {
+    server.setId("AU-TpxcB-iU5OvuD2FL7").setVersion("7.5.4");
+    MetricDto ncloc = db.measures().insertMetric(m -> m.setKey(NCLOC_KEY));
+    MetricDto nclocDistrib = db.measures().insertMetric(m -> m.setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY));
+
+    ProjectData projectData = db.components().insertPublicProject();
+
+    // default quality profile
+    QProfileDto javaQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("java"));
+    QProfileDto kotlinQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("kotlin"));
+    db.qualityProfiles().setAsDefault(javaQP,kotlinQP);
+    // selected quality profile
+    QProfileDto jsQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("js"));
+    db.qualityProfiles().associateWithProject(projectData.getProjectDto(), jsQP);
+
+
+    ComponentDto mainBranch = projectData.getMainBranchComponent();
+    db.measures().insertLiveMeasure(mainBranch, ncloc, m -> m.setValue(110d));
+    db.measures().insertLiveMeasure(mainBranch, nclocDistrib, m -> m.setValue(null).setData("java=70;js=30;kotlin=10"));
+
+    ComponentDto branch = db.components().insertProjectBranch(mainBranch, b -> b.setBranchType(BRANCH));
+    db.measures().insertLiveMeasure(branch, ncloc, m -> m.setValue(180d));
+    db.measures().insertLiveMeasure(branch, nclocDistrib, m -> m.setValue(null).setData("java=100;js=50;kotlin=30"));
+
+    SnapshotDto project1Analysis = db.components().insertSnapshot(mainBranch, t -> t.setLast(true));
+    SnapshotDto project2Analysis = db.components().insertSnapshot(branch, t -> t.setLast(true));
+    db.measures().insertMeasure(mainBranch, project1Analysis, nclocDistrib, m -> m.setData("java=70;js=30;kotlin=10"));
+    db.measures().insertMeasure(branch, project2Analysis, nclocDistrib, m -> m.setData("java=100;js=50;kotlin=30"));
+
+    TelemetryData data = communityUnderTest.load();
+
+    assertThat(data.getProjects()).extracting(TelemetryData.Project::projectUuid, TelemetryData.Project::language, TelemetryData.Project::qualityProfile)
+      .containsExactlyInAnyOrder(
+        tuple(projectData.projectUuid(), "java", javaQP.getKee()),
+        tuple(projectData.projectUuid(), "js", jsQP.getKee()),
+        tuple(projectData.projectUuid(), "kotlin", kotlinQP.getKee()));
+  }
+
   @Test
   public void test_ncd_on_community_edition() {
     server.setId("AU-TpxcB-iU5OvuD2FL7").setVersion("7.5.4");