]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20359 Add quality profiles to telemetry data
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Fri, 8 Sep 2023 13:31:08 +0000 (15:31 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 14 Sep 2023 20:02:39 +0000 (20:02 +0000)
12 files changed:
server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/QualityProfileDaoIT.java
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/it/java/org/sonar/server/telemetry/QualityProfileDataProviderIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/QualityProfileDataProvider.java [new file with mode: 0644]
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
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

index 60c7f5533a716ce094dbcb79d9e52124f650fb93..52591e688d4ba31a77f49dd672ebbffb4b69615c 100644 (file)
@@ -800,6 +800,52 @@ public class QualityProfileDaoIT {
     assertThat(underTest.selectProjectAssociations(dbSession, profile3, null)).hasSize(3);
   }
 
+  @Test
+  public void selectAllProjectAssociation_shouldReturnAllProjectsAndQualityProfiles() {
+    ProjectDto project1 = db.components().insertPrivateProject(t -> t.setName("Project1 name")).getProjectDto();
+    ProjectDto project2 = db.components().insertPrivateProject(t -> t.setName("Project2 name")).getProjectDto();
+    ProjectDto project3 = db.components().insertPrivateProject(t -> t.setName("Project3 name")).getProjectDto();
+    db.components().insertProjectBranch(project1, t -> t.setKey("branch"));
+
+    QProfileDto profile1 = newQualityProfileDto();
+    db.qualityProfiles().insert(profile1);
+    db.qualityProfiles().associateWithProject(project1, profile1);
+
+    QProfileDto profile2 = newQualityProfileDto();
+    db.qualityProfiles().insert(profile2);
+    db.qualityProfiles().associateWithProject(project2, profile2);
+    QProfileDto profile3 = newQualityProfileDto();
+
+    assertThat(underTest.selectAllProjectAssociations(dbSession))
+      .extracting("projectUuid", "projectKey", "projectName", "profileKey")
+      .containsExactlyInAnyOrder(
+        tuple(project1.getUuid(), project1.getKey(), project1.getName(), profile1.getKee()),
+        tuple(project2.getUuid(), project2.getKey(), project2.getName(), profile2.getKee())
+      );
+  }
+
+  @Test
+  public void selectAllDefaultProfiles_shouldReturnExpectedProfiles() {
+
+    QProfileDto defaultProfile1 = newQualityProfileDto();
+    db.qualityProfiles().insert(defaultProfile1);
+
+    QProfileDto defaultProfile2 = newQualityProfileDto();
+    db.qualityProfiles().insert(defaultProfile2);
+
+    QProfileDto otherProfile = newQualityProfileDto();
+    db.qualityProfiles().insert(otherProfile);
+
+    db.qualityProfiles().setAsDefault(defaultProfile1, defaultProfile2);
+
+    assertThat(underTest.selectAllDefaultProfiles(dbSession))
+      .extracting("kee", "name", "language")
+      .containsExactlyInAnyOrder(
+        tuple(defaultProfile1.getKee(), defaultProfile1.getName(), defaultProfile1.getLanguage()),
+        tuple(defaultProfile2.getKee(), defaultProfile2.getName(), defaultProfile2.getLanguage())
+      );
+  }
+
   @Test
   public void selectUuidsOfCustomRulesProfiles_returns_the_custom_profiles_with_specified_name() {
     QProfileDto outdatedProfile1 = db.qualityProfiles().insert(p -> p.setIsBuiltIn(false).setLanguage("java").setName("foo"));
index 4aa6fae745f83f3a03006cbcf6ec13541f74f538..b392a9b8de56e65f60c966ce5ac12cee3a596276 100644 (file)
@@ -144,6 +144,10 @@ public class QualityProfileDao implements Dao {
     return executeLargeInputs(languages, partition -> mapper(dbSession).selectDefaultProfiles(partition));
   }
 
+  public List<QProfileDto> selectAllDefaultProfiles(DbSession dbSession) {
+    return mapper(dbSession).selectAllDefaultProfiles();
+  }
+
   public List<QProfileDto> selectDefaultBuiltInProfilesWithoutActiveRules(DbSession dbSession, Set<String> languages) {
     return executeLargeInputs(languages, partition -> mapper(dbSession).selectDefaultBuiltInProfilesWithoutActiveRules(partition));
   }
@@ -244,6 +248,9 @@ public class QualityProfileDao implements Dao {
     return mapper(dbSession).selectProjectAssociations(profile.getKee(), nameQuery);
   }
 
+  public List<ProjectQprofileAssociationDto> selectAllProjectAssociations(DbSession dbSession) {
+    return mapper(dbSession).selectAllProjectAssociations();
+  }
   public Collection<String> selectUuidsOfCustomRulesProfiles(DbSession dbSession, String language, String name) {
     return mapper(dbSession).selectUuidsOfCustomRuleProfiles(language, name);
   }
index 13a3e690a1893fb8adcd1703bc57d07fe38c3002..0fe72c5c5956cb45fab17427ac9fac272b782d6d 100644 (file)
@@ -57,6 +57,8 @@ public interface QualityProfileMapper {
   List<QProfileDto> selectDefaultProfiles(
     @Param("languages") Collection<String> languages);
 
+  List<QProfileDto> selectAllDefaultProfiles();
+
   @CheckForNull
   String selectDefaultProfileUuid(@Param("language") String language);
 
@@ -107,7 +109,6 @@ 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(
@@ -122,6 +123,8 @@ public interface QualityProfileMapper {
     @Param("profileUuid") String profileUuid,
     @Param("nameOrKeyQuery") String nameOrKeyQuery);
 
+  List<ProjectQprofileAssociationDto> selectAllProjectAssociations();
+
   List<String> selectUuidsOfCustomRuleProfiles(@Param("language") String language, @Param("name") String name);
 
   void renameRuleProfiles(@Param("newName") String newName, @Param("updatedAt") Date updatedAt, @Param("uuids") Collection<String> uuids);
index e68c9261ef41052bf9c11bf8415b8279c399ddbb..3a461b608cbfe2d0cbc05d1b50819d0edb71beb6 100644 (file)
       and rp.language = dp.language
   </select>
 
+  <select id="selectAllDefaultProfiles" parameterType="map" resultType="org.sonar.db.qualityprofile.QProfileDto">
+    select
+    <include refid="qProfileColumns"/>
+    from org_qprofiles oqp
+    inner join rules_profiles rp on oqp.rules_profile_uuid = rp.uuid
+    inner join default_qprofiles dp on dp.qprofile_uuid = oqp.uuid
+  </select>
+
   <select id="selectBuiltInRuleProfilesWithActiveRules" resultType="org.sonar.db.qualityprofile.RulesProfileDto">
     select <include refid="ruleProfileColumns"/>
     from rules_profiles rp
     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>
+
   <select id="selectUuidsOfCustomRuleProfiles" parameterType="map" resultType="string">
     select oqp.rules_profile_uuid
     from org_qprofiles oqp
index 6bd08675155a243565bcfd02d70d2eb178c9c528..818d0c11ea4bd7c06a1ece41fdc13828094578e6 100644 (file)
@@ -52,6 +52,7 @@ public class TelemetryData {
   private final List<ProjectStatistics> projectStatistics;
   private final List<Branch> branches;
   private final List<QualityGate> qualityGates;
+  private final List<QualityProfile> qualityProfiles;
   private final Collection<NewCodeDefinition> newCodeDefinitions;
   private final Boolean hasUnanalyzedC;
   private final Boolean hasUnanalyzedCpp;
@@ -73,6 +74,7 @@ public class TelemetryData {
     projects = builder.projects;
     projectStatistics = builder.projectStatistics;
     qualityGates = builder.qualityGates;
+    qualityProfiles = builder.qualityProfiles;
     hasUnanalyzedC = builder.hasUnanalyzedC;
     hasUnanalyzedCpp = builder.hasUnanalyzedCpp;
     customSecurityConfigs = requireNonNullElse(builder.customSecurityConfigs, Set.of());
@@ -159,6 +161,10 @@ public class TelemetryData {
     return qualityGates;
   }
 
+  public List<QualityProfile> getQualityProfiles() {
+    return qualityProfiles;
+  }
+
   static Builder builder() {
     return new Builder();
   }
@@ -197,6 +203,7 @@ public class TelemetryData {
     private List<Branch> branches;
     private Collection<NewCodeDefinition> newCodeDefinitions;
     private List<QualityGate> qualityGates;
+    private List<QualityProfile> qualityProfiles;
     private int ncdId;
 
     private Builder() {
@@ -303,6 +310,12 @@ public class TelemetryData {
       return this;
     }
 
+
+    Builder setQualityProfiles(List<QualityProfile> qualityProfiles) {
+      this.qualityProfiles = qualityProfiles;
+      return this;
+    }
+
     Builder setNcdId(int ncdId) {
       this.ncdId = ncdId;
       return this;
@@ -349,10 +362,18 @@ public class TelemetryData {
   record QualityGate(String uuid, String caycStatus) {
   }
 
+  public record QualityProfile(String uuid, @Nullable String parentUuid, String language, boolean isDefault,
+                               boolean isBuiltIn,
+                        @Nullable Boolean builtInParent, @Nullable Integer rulesOverriddenCount,
+                        @Nullable Integer rulesActivatedCount, @Nullable Integer rulesDeactivatedCount
+  ) {
+  }
+
   record ManagedInstanceInformation(boolean isManaged, @Nullable String provider) {
   }
 
-  record CloudUsage(boolean kubernetes, @Nullable String kubernetesVersion, @Nullable String kubernetesPlatform, @Nullable String kubernetesProvider,
+  record CloudUsage(boolean kubernetes, @Nullable String kubernetesVersion, @Nullable String kubernetesPlatform,
+                    @Nullable String kubernetesProvider,
                     @Nullable String officialHelmChart, @Nullable String containerRuntime, boolean officialImage) {
   }
 
index 77cd481b47458cc5e1a28cb649236389a8896b9b..357ec98ac3a063f38c3d7da54081702a206d1124 100644 (file)
@@ -102,6 +102,7 @@ public class TelemetryDataJsonWriter {
     writeBranches(json, telemetryData);
     writeNewCodeDefinitions(json, telemetryData);
     writeQualityGates(json, telemetryData);
+    writeQualityProfiles(json, telemetryData);
     writeManagedInstanceInformation(json, telemetryData.getManagedInstanceInformation());
     writeCloudUsage(json, telemetryData.getCloudUsage());
     extensions.forEach(e -> e.write(json));
@@ -225,6 +226,28 @@ public class TelemetryDataJsonWriter {
     }
   }
 
+  private static void writeQualityProfiles(JsonWriter json, TelemetryData telemetryData) {
+    if (telemetryData.getQualityProfiles() != null) {
+      json.name("quality-profiles");
+      json.beginArray();
+      telemetryData.getQualityProfiles().forEach(qualityProfile -> {
+        json.beginObject();
+        json.prop("uuid", qualityProfile.uuid());
+        json.prop("parentUuid", qualityProfile.parentUuid());
+        json.prop(LANGUAGE_PROPERTY, qualityProfile.language());
+        json.prop("default", qualityProfile.isDefault());
+        json.prop("builtIn", qualityProfile.isBuiltIn());
+        if (qualityProfile.builtInParent() != null) {
+          json.prop("builtInParent", qualityProfile.builtInParent());
+        }
+        json.prop("rulesOverriddenCount", qualityProfile.rulesOverriddenCount());
+        json.prop("rulesActivatedCount", qualityProfile.rulesActivatedCount());
+        json.prop("rulesDeactivatedCount", qualityProfile.rulesDeactivatedCount());
+        json.endObject();
+      });
+      json.endArray();
+    }
+  }
   private static void writeManagedInstanceInformation(JsonWriter json, TelemetryData.ManagedInstanceInformation provider) {
     json.name(MANAGED_INSTANCE_PROPERTY);
     json.beginObject();
index 2108dbaa5da019495a25b7ac22c34590a7b81244..120d8ca295c5959aabf974b8c54d27e7d133fb0b 100644 (file)
@@ -531,6 +531,40 @@ public class TelemetryDataJsonWriterTest {
     );
   }
 
+  @Test
+  public void writeTelemetryData_shouldWriteQualityProfiles() {
+    TelemetryData data = telemetryBuilder()
+      .setQualityProfiles(List.of(
+        new TelemetryData.QualityProfile("uuid-1", "parent-uuid-1", "js", true, false, true, 2, 3, 4),
+        new TelemetryData.QualityProfile("uuid-1", null, "js", false, true, null, null, null, null)))
+      .build();
+
+    String json = writeTelemetryData(data);
+    assertJson(json).isSimilarTo("""
+      {
+        "quality-profiles": [
+          {
+            "uuid": "uuid-1",
+            "parentUuid": "parent-uuid-1",
+            "language": "js",
+            "default": true,
+            "builtIn": false,
+            "builtInParent": true,
+            "rulesOverriddenCount": 2,
+            "rulesActivatedCount": 3,
+            "rulesDeactivatedCount": 4
+          },
+          {
+            "uuid": "uuid-1",
+            "language": "js",
+            "default": false,
+            "builtIn": true
+          }
+      ]}
+      """
+    );
+  }
+
   @Test
   public void writes_all_branches() {
     TelemetryData data = telemetryBuilder()
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/QualityProfileDataProviderIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/QualityProfileDataProviderIT.java
new file mode 100644 (file)
index 0000000..65cbcbf
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * 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.server.telemetry;
+
+import javax.annotation.Nullable;
+import org.assertj.core.api.Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ProjectData;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.qualityprofile.QProfileComparison;
+
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.sonar.db.qualityprofile.ActiveRuleDto.OVERRIDES;
+
+public class QualityProfileDataProviderIT {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = dbTester.getDbClient();
+
+  QualityProfileDataProvider underTest = new QualityProfileDataProvider(dbClient, new QProfileComparison(dbClient));
+
+  @Test
+  public void retrieveQualityProfilesData_whenDefaultRootProfile_shouldReturnRelevantInformation() {
+    QProfileDto qProfile1 = createQualityProfile(false, null);
+    dbTester.qualityProfiles().setAsDefault(qProfile1);
+    Assertions.assertThat(underTest.retrieveQualityProfilesData())
+      .extracting(p -> p.uuid(), p -> p.isDefault(), p -> p.isBuiltIn(), p -> p.builtInParent(),
+        p -> p.rulesActivatedCount(), p -> p.rulesDeactivatedCount(), p -> p.rulesOverriddenCount())
+      .containsExactlyInAnyOrder(tuple(qProfile1.getKee(), true, false, false, null, null, null));
+  }
+
+  @Test
+  public void retrieveQualityProfilesData_whenDefaultChildProfile_shouldReturnRelevantInformation() {
+    QProfileDto rootProfile = createQualityProfile(false, null);
+
+    QProfileDto childProfile = createQualityProfile(false, rootProfile.getKee());
+
+    dbTester.qualityProfiles().setAsDefault(childProfile);
+    Assertions.assertThat(underTest.retrieveQualityProfilesData())
+      .extracting(p -> p.uuid(), p -> p.isDefault(), p -> p.isBuiltIn(), p -> p.builtInParent(),
+        p -> p.rulesActivatedCount(), p -> p.rulesDeactivatedCount(), p -> p.rulesOverriddenCount())
+      .containsExactlyInAnyOrder(
+        tuple(rootProfile.getKee(), false, false, false, null, null, null),
+        tuple(childProfile.getKee(), true, false, false, null, null, null));
+  }
+
+  @Test
+  public void retrieveQualityProfilesData_whenProfileAssignedToProject_shouldReturnProfile() {
+    ProjectData projectData = dbTester.components().insertPublicProject();
+
+    QProfileDto associatedProfile = createQualityProfile(false, null);
+
+    QProfileDto unassociatedProfile = createQualityProfile(false, null);
+
+    dbTester.qualityProfiles().associateWithProject(projectData.getProjectDto(), associatedProfile);
+
+    Assertions.assertThat(underTest.retrieveQualityProfilesData())
+      .extracting(p -> p.uuid(), p -> p.isDefault())
+      .containsExactlyInAnyOrder(
+        tuple(associatedProfile.getKee(), false),
+        tuple(unassociatedProfile.getKee(), false)
+      );
+  }
+
+  @Test
+  public void retrieveQualityProfilesData_whenBuiltInParent_shouldReturnBuiltInParent() {
+
+    QProfileDto rootBuiltinProfile = createQualityProfile(true, null);
+
+    QProfileDto childProfile = createQualityProfile(false, rootBuiltinProfile.getKee());
+
+    QProfileDto grandChildProfile = createQualityProfile(false, childProfile.getKee());
+
+    dbTester.qualityProfiles().setAsDefault(rootBuiltinProfile, childProfile, grandChildProfile);
+
+    Assertions.assertThat(underTest.retrieveQualityProfilesData())
+      .extracting(p -> p.uuid(), p -> p.isBuiltIn(), p -> p.builtInParent())
+      .containsExactlyInAnyOrder(tuple(rootBuiltinProfile.getKee(), true, null),
+        tuple(childProfile.getKee(), false, true),
+        tuple(grandChildProfile.getKee(), false, true)
+      );
+  }
+
+  @Test
+  public void retrieveQualityProfilesData_whenBuiltInParent_shouldReturnActiveAndUnactiveRules() {
+
+    QProfileDto rootBuiltinProfile = createQualityProfile(true, null);
+
+    QProfileDto childProfile = createQualityProfile(false, rootBuiltinProfile.getKee());
+    RuleDto activatedRule = dbTester.rules().insert();
+    RuleDto deactivatedRule = dbTester.rules().insert();
+
+    dbTester.qualityProfiles().activateRule(rootBuiltinProfile, deactivatedRule);
+    dbTester.qualityProfiles().activateRule(childProfile, activatedRule);
+    dbTester.qualityProfiles().setAsDefault(childProfile);
+
+    Assertions.assertThat(underTest.retrieveQualityProfilesData())
+      .extracting(p -> p.uuid(), p -> p.rulesActivatedCount(), p -> p.rulesDeactivatedCount(), p -> p.rulesOverriddenCount())
+      .containsExactlyInAnyOrder(
+        tuple(rootBuiltinProfile.getKee(), null, null, null),
+        tuple(childProfile.getKee(), 1, 1, 0)
+      );
+  }
+
+  @Test
+  public void retrieveQualityProfilesData_whenBuiltInParent_shouldReturnOverriddenRules() {
+
+    QProfileDto rootBuiltinProfile = createQualityProfile(true, null);
+
+    QProfileDto childProfile = createQualityProfile(false, rootBuiltinProfile.getKee());
+    RuleDto rule = dbTester.rules().insert();
+    RuleParamDto initialRuleParam = dbTester.rules().insertRuleParam(rule, p -> p.setName("key").setDefaultValue("initial"));
+
+
+    ActiveRuleDto activeRuleDto = dbTester.qualityProfiles().activateRule(rootBuiltinProfile, rule);
+    dbTester.getDbClient().activeRuleDao().insertParam(dbTester.getSession(), activeRuleDto, newParam(activeRuleDto, initialRuleParam, "key", "value"));
+
+    ActiveRuleDto childActivateRule = dbTester.qualityProfiles().activateRule(childProfile, rule, ar -> {
+      ar.setInheritance(OVERRIDES);
+    });
+    dbTester.getDbClient().activeRuleDao().insertParam(dbTester.getSession(), activeRuleDto, newParam(childActivateRule, initialRuleParam, "key", "override"));
+
+    dbTester.qualityProfiles().setAsDefault(childProfile);
+
+    Assertions.assertThat(underTest.retrieveQualityProfilesData())
+      .extracting(p -> p.uuid(), p -> p.rulesActivatedCount(), p -> p.rulesDeactivatedCount(), p -> p.rulesOverriddenCount())
+      .containsExactlyInAnyOrder(
+        tuple(rootBuiltinProfile.getKee(), null, null, null),
+        tuple(childProfile.getKee(), 0, 0, 1));
+  }
+
+  private static ActiveRuleParamDto newParam(ActiveRuleDto activeRuleDto, RuleParamDto initial, String key, String value) {
+    return new ActiveRuleParamDto().setActiveRuleUuid(activeRuleDto.getRuleUuid()).setRulesParameterUuid(initial.getUuid()).setKey(key).setValue(value);
+  }
+
+  private QProfileDto createQualityProfile(boolean isBuiltIn, @Nullable String parentKee) {
+    return dbTester.qualityProfiles().insert(p -> {
+      p.setIsBuiltIn(isBuiltIn);
+      p.setParentKee(parentKee);
+    });
+  }
+}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/QualityProfileDataProvider.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/QualityProfileDataProvider.java
new file mode 100644 (file)
index 0000000..040c3a5
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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.server.telemetry;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.server.qualityprofile.QProfileComparison;
+
+import static java.util.stream.Collectors.toMap;
+
+public class QualityProfileDataProvider {
+
+  private final DbClient dbClient;
+  private final QProfileComparison qProfileComparison;
+
+  public QualityProfileDataProvider(DbClient dbClient, QProfileComparison qProfileComparison) {
+    this.dbClient = dbClient;
+    this.qProfileComparison = qProfileComparison;
+  }
+
+  public List<TelemetryData.QualityProfile> retrieveQualityProfilesData() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+
+      Set<String> defaultProfileUuids = dbClient.qualityProfileDao().selectAllDefaultProfiles(dbSession)
+        .stream().map(QProfileDto::getKee)
+        .collect(Collectors.toSet());
+
+      Map<String, QProfileDto> allProfileDtosByUuid = dbClient.qualityProfileDao().selectAll(dbSession)
+        .stream()
+        .collect(toMap(QProfileDto::getKee, p -> p));
+
+      return allProfileDtosByUuid.entrySet().stream()
+        .map(p -> mapQualityProfile(p.getValue(), allProfileDtosByUuid, defaultProfileUuids.contains(p.getKey()), dbSession))
+        .toList();
+    }
+  }
+
+  private TelemetryData.QualityProfile mapQualityProfile(QProfileDto profile, Map<String, QProfileDto> allProfileDtos, boolean isDefault, DbSession dbSession) {
+    QProfileDto rootProfile = getRootProfile(profile.getKee(), allProfileDtos);
+    Boolean isBuiltInRootParent;
+    if (profile.isBuiltIn()) {
+      isBuiltInRootParent = null;
+    } else {
+      isBuiltInRootParent = rootProfile.isBuiltIn() && !rootProfile.getKee().equals(profile.getKee());
+    }
+
+    Optional<QProfileComparison.QProfileComparisonResult> rulesComparison = Optional.of(profile)
+      .filter(p -> isBuiltInRootParent != null && isBuiltInRootParent)
+      .map(p -> qProfileComparison.compare(dbSession, rootProfile, profile));
+
+    return new TelemetryData.QualityProfile(profile.getKee(),
+      profile.getParentKee(),
+      profile.getLanguage(),
+      isDefault,
+      profile.isBuiltIn(),
+      isBuiltInRootParent,
+      rulesComparison.map(c -> c.modified().size()).orElse(null),
+      rulesComparison.map(c -> c.inRight().size()).orElse(null),
+      rulesComparison.map(c -> c.inLeft().size()).orElse(null)
+    );
+  }
+
+  public QProfileDto getRootProfile(String kee, Map<String, QProfileDto> allProfileDtos) {
+    QProfileDto qProfileDto = allProfileDtos.get(kee);
+    String parentKee = qProfileDto.getParentKee();
+    if (parentKee != null) {
+      return getRootProfile(parentKee, allProfileDtos);
+    } else {
+      return allProfileDtos.get(kee);
+    }
+  }
+}
index 27009b236827897f5a61a6b15da5ee7700a4b289..6382bef3174767c96ee65620c953f9707f03efa5 100644 (file)
@@ -112,6 +112,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   private final QualityGateFinder qualityGateFinder;
   private final ManagedInstanceService managedInstanceService;
   private final CloudUsageDataProvider cloudUsageDataProvider;
+  private final QualityProfileDataProvider qualityProfileDataProvider;
   private final Set<NewCodeDefinition> newCodeDefinitions = new HashSet<>();
   private final Map<String, NewCodeDefinition> ncdByProject = new HashMap<>();
   private final Map<String, NewCodeDefinition> ncdByBranch = new HashMap<>();
@@ -121,7 +122,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository,
     PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration,
     ContainerSupport containerSupport, QualityGateCaycChecker qualityGateCaycChecker, QualityGateFinder qualityGateFinder,
-    ManagedInstanceService managedInstanceService, CloudUsageDataProvider cloudUsageDataProvider) {
+    ManagedInstanceService managedInstanceService, CloudUsageDataProvider cloudUsageDataProvider, QualityProfileDataProvider qualityProfileDataProvider) {
     this.server = server;
     this.dbClient = dbClient;
     this.pluginRepository = pluginRepository;
@@ -133,6 +134,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     this.qualityGateFinder = qualityGateFinder;
     this.managedInstanceService = managedInstanceService;
     this.cloudUsageDataProvider = cloudUsageDataProvider;
+    this.qualityProfileDataProvider = qualityProfileDataProvider;
   }
 
   private static Database loadDatabaseMetadata(DbSession dbSession) {
@@ -174,6 +176,8 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
       resolveUsers(data, dbSession);
     }
 
+    data.setQualityProfiles(qualityProfileDataProvider.retrieveQualityProfilesData());
+
     setSecurityCustomConfigIfPresent(data);
 
     Optional<String> installationDateProperty = internalProperties.read(InternalProperties.INSTALLATION_DATE);
@@ -365,6 +369,8 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     data.setQualityGates(qualityGates);
   }
 
+
+
   private void resolveUsers(TelemetryData.Builder data, DbSession dbSession) {
     data.setUsers(dbClient.userDao().selectUsersForTelemetry(dbSession));
   }
index 3a1db553b7573c3f87534e01d1f052a5442ed52d..edf532d205eda7fa3ce26791c3297699238ee4ef 100644 (file)
@@ -46,7 +46,6 @@ import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.alm.setting.AlmSettingDto;
 import org.sonar.db.component.AnalysisPropertyDto;
-import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ProjectData;
 import org.sonar.db.component.SnapshotDto;
@@ -54,6 +53,7 @@ import org.sonar.db.metric.MetricDto;
 import org.sonar.db.newcodeperiod.NewCodePeriodType;
 import org.sonar.db.property.PropertyDto;
 import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.db.qualityprofile.QProfileDto;
 import org.sonar.db.user.UserDbTester;
 import org.sonar.db.user.UserDto;
 import org.sonar.db.user.UserTelemetryDto;
@@ -63,6 +63,7 @@ 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.server.qualityprofile.QProfileComparison;
 import org.sonar.server.telemetry.TelemetryData.Branch;
 import org.sonar.server.telemetry.TelemetryData.CloudUsage;
 import org.sonar.server.telemetry.TelemetryData.NewCodeDefinition;
@@ -114,14 +115,16 @@ public class TelemetryDataLoaderImplTest {
   private final ContainerSupport containerSupport = mock(ContainerSupport.class);
   private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
   private final QualityGateFinder qualityGateFinder = new QualityGateFinder(db.getDbClient());
+
+  private final QualityProfileDataProvider qualityProfileDataProvider = new QualityProfileDataProvider(db.getDbClient(), new QProfileComparison(db.getDbClient()));
   private final InternalProperties internalProperties = spy(new MapInternalProperties());
   private final ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class);
   private final CloudUsageDataProvider cloudUsageDataProvider = mock(CloudUsageDataProvider.class);
 
   private final TelemetryDataLoader communityUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
-    internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider);
+    internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider, qualityProfileDataProvider);
   private final TelemetryDataLoader commercialUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
-    internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider);
+    internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider, qualityProfileDataProvider);
 
   private QualityGateDto builtInDefaultQualityGate;
   private MetricDto bugsDto;
@@ -215,6 +218,11 @@ public class TelemetryDataLoaderImplTest {
     QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG1").setBuiltIn(true));
     QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG2"));
 
+    //quality profiles
+    QProfileDto qualityProfile1 = db.qualityProfiles().insert(qp -> qp.setIsBuiltIn(true));
+    QProfileDto qualityProfile2 = db.qualityProfiles().insert();
+    db.qualityProfiles().setAsDefault(qualityProfile1, qualityProfile2);
+
     // link one project to a non-default QG
     db.qualityGates().associateProjectToQualityGate(db.components().getProjectDtoByMainBranch(mainBranch1), qualityGate1);
 
@@ -285,6 +293,11 @@ public class TelemetryDataLoaderImplTest {
         tuple(qualityGate1.getUuid(), "non-compliant"),
         tuple(qualityGate2.getUuid(), "non-compliant")
       );
+
+    assertThat(data.getQualityProfiles())
+      .extracting(TelemetryData.QualityProfile::uuid, TelemetryData.QualityProfile::isBuiltIn)
+      .containsExactlyInAnyOrder(tuple(qualityProfile1.getKee(), qualityProfile1.isBuiltIn()), tuple(qualityProfile2.getKee(), qualityProfile2.isBuiltIn()));
+
   }
 
   @Test
@@ -622,7 +635,7 @@ public class TelemetryDataLoaderImplTest {
 
   @DataProvider
   public static Object[][] getManagedInstanceData() {
-    return new Object[][] {
+    return new Object[][]{
       {true, "scim"},
       {true, "github"},
       {true, "gitlab"},
index 9a286b3e2ef888d969057bdd0ff2e6ab19f775a4..7345e963bf0ddb6f5655120222b866cc22280fd6 100644 (file)
@@ -238,6 +238,7 @@ import org.sonar.server.setting.ws.SettingsWsModule;
 import org.sonar.server.source.ws.SourceWsModule;
 import org.sonar.server.startup.LogServerId;
 import org.sonar.server.telemetry.CloudUsageDataProvider;
+import org.sonar.server.telemetry.QualityProfileDataProvider;
 import org.sonar.server.telemetry.TelemetryClient;
 import org.sonar.server.telemetry.TelemetryDaemon;
 import org.sonar.server.telemetry.TelemetryDataJsonWriter;
@@ -620,6 +621,7 @@ public class PlatformLevel4 extends PlatformLevel {
       TelemetryDaemon.class,
       TelemetryClient.class,
       CloudUsageDataProvider.class,
+      QualityProfileDataProvider.class,
 
       // monitoring
       ServerMonitoringMetrics.class,