]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19297 Track new code definition in Telemetry
authorZipeng WU <zipeng.wu@sonarsource.com>
Tue, 23 May 2023 08:24:56 +0000 (10:24 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 25 May 2023 20:02:51 +0000 (20:02 +0000)
Co-authored-by: Zipeng WU <zipeng.wu@sonarsource.com>
Co-authored-by: Nolwenn Cadic <nolwenn.cadic@sonarsource.com>
13 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/newcodeperiod/NewCodePeriodMapper.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/main/java/org/sonar/server/telemetry/TelemetryDataLoader.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/TelemetryDaemon.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 337a3ec94238a2ab230fc091168450b256b54670..609944cdb6da9b0720acbebb7053880ae4da1e7c 100644 (file)
@@ -198,4 +198,8 @@ public class BranchDao implements Dao {
       .map(BranchDto::isNeedIssueSync)
       .orElse(false);
   }
+
+  public List<BranchDto> selectAllBranches(DbSession dbSession) {
+    return mapper(dbSession).selectAllBranches();
+  }
 }
index 57bd3acd77e7050cd0f4545fde9657c24fa4311a..86489d34b07742dcc08efe27c8d60bcff130506c 100644 (file)
@@ -76,4 +76,6 @@ public interface BranchMapper {
   Optional<BranchDto> selectMainBranchByProjectUuid(String projectUuid);
 
   List<BranchDto> selectMainBranchesByProjectUuids(@Param("projectUuids") Collection<String> projectUuids);
+
+  List<BranchDto> selectAllBranches();
 }
index 7b1beabc7d2ebfbb67e4574b2ebb7d057165601b..f3d680ed0951be35c8fadf0788bfcf5e44da2cd6 100644 (file)
@@ -112,4 +112,7 @@ public class NewCodePeriodDao implements Dao {
     return session.getMapper(NewCodePeriodMapper.class);
   }
 
+  public List<NewCodePeriodDto> selectAll(DbSession dbSession) {
+    return mapper(dbSession).selectAll();
+  }
 }
index b9f90f220c7f7ab48f5ce71c05aa3328c14b7d1e..ad61af11c51b28a49590b8ca8592ca08379e0cb1 100644 (file)
@@ -45,4 +45,6 @@ public interface NewCodePeriodMapper {
   long countByProjectAnalysis(String projectAnalysisUuid);
 
   List<NewCodePeriodDto> selectAllByProject(String projectUuid);
+
+  List<NewCodePeriodDto> selectAll();
 }
index 56d6036d4b0968baea4c17e4b0d54d11a3561c8d..140e3ecfd2cf84ee35391e0292c63cdf37ff04aa 100644 (file)
     </foreach>
   </select>
 
+  <select id="selectAllBranches" resultType="org.sonar.db.component.BranchDto">
+    select
+    <include refid="columns"/>
+    from project_branches pb
+    where branch_type='BRANCH'
+  </select>
+
   <select id="selectByProjectUuid" parameterType="string" resultType="org.sonar.db.component.BranchDto">
     select <include refid="columns"/>
     from project_branches pb
     pb.project_uuid = #{projectUuid, jdbcType=VARCHAR}
   </select>
 
+
   <select id="selectMainBranchByProjectUuid" resultType="org.sonar.db.component.BranchDto">
     select <include refid="columns"/>
     from project_branches pb
index 64bc8513c3b3435788920150eeeb8e325295f14a..2a27f115aa40a7a40ae3a3a6dddedc633141fe2b 100644 (file)
     AND ncp.branch_uuid is null
   </select>
 
+  <select id="selectAll" parameterType="map" resultType="org.sonar.db.newcodeperiod.NewCodePeriodDto">
+    SELECT
+    <include refid="newCodePeriodMapperColumns"/>
+    FROM new_code_periods ncp
+  </select>
+
   <select id="selectAllByProject" parameterType="map" resultType="org.sonar.db.newcodeperiod.NewCodePeriodDto">
     SELECT
     <include refid="newCodePeriodMapperColumns"/>
index c36e91ea3332d31393c52e00900d119e33f71cb2..fa554c8a2516738551c94a76c1151776418c03f7 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.telemetry;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -31,6 +32,7 @@ import org.sonar.core.platform.EditionProvider.Edition;
 import org.sonar.db.user.UserTelemetryDto;
 
 import static java.util.Objects.requireNonNullElse;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
 
 public class TelemetryData {
   private final String serverId;
@@ -47,9 +49,12 @@ public class TelemetryData {
   private final List<UserTelemetryDto> users;
   private final List<Project> projects;
   private final List<ProjectStatistics> projectStatistics;
+  private final List<Branch> branches;
   private final List<QualityGate> qualityGates;
+  private final Collection<NewCodeDefinition> newCodeDefinitions;
   private final Boolean hasUnanalyzedC;
   private final Boolean hasUnanalyzedCpp;
+  private final int ncdId;
   private final Set<String> customSecurityConfigs;
 
   private TelemetryData(Builder builder) {
@@ -71,6 +76,9 @@ public class TelemetryData {
     hasUnanalyzedCpp = builder.hasUnanalyzedCpp;
     customSecurityConfigs = requireNonNullElse(builder.customSecurityConfigs, Set.of());
     managedInstanceInformation = builder.managedInstanceInformation;
+    ncdId = builder.ncdId;
+    branches = builder.branches;
+    newCodeDefinitions = builder.newCodeDefinitions;
   }
 
   public String getServerId() {
@@ -149,6 +157,18 @@ public class TelemetryData {
     return new Builder();
   }
 
+  public int getNcdId() {
+    return ncdId;
+  }
+
+  public List<Branch> getBranches() {
+    return branches;
+  }
+
+  public Collection<NewCodeDefinition> getNewCodeDefinitions() {
+    return newCodeDefinitions;
+  }
+
   static class Builder {
     private String serverId;
     private String version;
@@ -167,7 +187,10 @@ public class TelemetryData {
     private List<UserTelemetryDto> users;
     private List<Project> projects;
     private List<ProjectStatistics> projectStatistics;
+    private List<Branch> branches;
+    private Collection<NewCodeDefinition> newCodeDefinitions;
     private List<QualityGate> qualityGates;
+    private int ncdId;
 
     private Builder() {
       // enforce static factory method
@@ -268,15 +291,46 @@ public class TelemetryData {
       return this;
     }
 
+    Builder setNcdId(int ncdId) {
+      this.ncdId = ncdId;
+      return this;
+    }
+
     private static void requireNonNullValues(Object... values) {
       Arrays.stream(values).forEach(Objects::requireNonNull);
     }
 
+    Builder setBranches(List<Branch> branches) {
+      this.branches = branches;
+      return this;
+    }
+
+    Builder setNewCodeDefinitions(Collection<NewCodeDefinition> newCodeDefinitions) {
+      this.newCodeDefinitions = newCodeDefinitions;
+      return this;
+    }
   }
 
   record Database(String name, String version) {
   }
 
+  record NewCodeDefinition(String type, @Nullable String value, String scope) {
+
+    private static final NewCodeDefinition instanceDefault = new NewCodeDefinition(PREVIOUS_VERSION.name(), "", "instance");
+
+    public static NewCodeDefinition getInstanceDefault() {
+      return instanceDefault;
+    }
+
+    @Override
+    public String value() {
+      return value == null ? "" : value;
+    }
+  }
+
+  record Branch(String projectUuid, String branchUuid, int ncdId) {
+  }
+
   record Project(String projectUuid, Long lastAnalysis, String language, Long loc) {
   }
 
@@ -300,6 +354,8 @@ public class TelemetryData {
     private final Long technicalDebt;
     private final Long developmentCost;
 
+    private final int ncdId;
+
     ProjectStatistics(Builder builder) {
       this.projectUuid = builder.projectUuid;
       this.branchCount = builder.branchCount;
@@ -313,6 +369,11 @@ public class TelemetryData {
       this.securityHotspots = builder.securityHotspots;
       this.technicalDebt = builder.technicalDebt;
       this.developmentCost = builder.developmentCost;
+      this.ncdId = builder.ncdId;
+    }
+
+    public int getNcdId() {
+      return ncdId;
     }
 
     public String getProjectUuid() {
@@ -376,12 +437,18 @@ public class TelemetryData {
       private Long securityHotspots;
       private Long technicalDebt;
       private Long developmentCost;
+      private int ncdId;
 
       public Builder setProjectUuid(String projectUuid) {
         this.projectUuid = projectUuid;
         return this;
       }
 
+      public Builder setNcdId(int ncdId) {
+        this.ncdId = ncdId;
+        return this;
+      }
+
       public Builder setBranchCount(Long branchCount) {
         this.branchCount = branchCount;
         return this;
index b4a5eafbe09df42fe66f483a601cdd17614a2d67..dfdf7fc073edb79bc3a3ed768d852204a7b6654c 100644 (file)
@@ -40,6 +40,8 @@ public class TelemetryDataJsonWriter {
 
   private static final String LANGUAGE_PROPERTY = "language";
   private static final String VERSION = "version";
+  private static final String NCD_ID = "ncdId";
+  private static final String PROJECT_ID = "projectUuid";
 
   private final List<TelemetryExtension> extensions;
 
@@ -56,6 +58,7 @@ public class TelemetryDataJsonWriter {
     json.prop(VERSION, telemetryData.getVersion());
     json.prop("messageSequenceNumber", telemetryData.getMessageSequenceNumber());
     json.prop("localTimestamp", toUtc(system2.now()));
+    json.prop(NCD_ID, telemetryData.getNcdId());
     telemetryData.getEdition().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH)));
     json.prop("defaultQualityGate", telemetryData.getDefaultQualityGate());
     json.name("database");
@@ -94,6 +97,8 @@ public class TelemetryDataJsonWriter {
     writeUserData(json, telemetryData);
     writeProjectData(json, telemetryData);
     writeProjectStatsData(json, telemetryData);
+    writeBranches(json, telemetryData);
+    writeNewCodeDefinitions(json, telemetryData);
     writeQualityGates(json, telemetryData);
     writeManagedInstanceInformation(json, telemetryData.getManagedInstanceInformation());
     extensions.forEach(e -> e.write(json));
@@ -131,7 +136,7 @@ public class TelemetryDataJsonWriter {
       json.beginArray();
       telemetryData.getProjects().forEach(project -> {
         json.beginObject();
-        json.prop("projectUuid", project.projectUuid());
+        json.prop(PROJECT_ID, project.projectUuid());
         if (project.lastAnalysis() != null) {
           json.prop("lastAnalysis", toUtc(project.lastAnalysis()));
         }
@@ -143,19 +148,51 @@ public class TelemetryDataJsonWriter {
     }
   }
 
+  private static void writeBranches(JsonWriter json, TelemetryData telemetryData) {
+    if (telemetryData.getBranches() != null) {
+      json.name("branches");
+      json.beginArray();
+      telemetryData.getBranches().forEach(branch -> {
+        json.beginObject();
+        json.prop(PROJECT_ID, branch.projectUuid());
+        json.prop("branchUuid", branch.branchUuid());
+        json.prop(NCD_ID, branch.ncdId());
+        json.endObject();
+      });
+      json.endArray();
+    }
+  }
+
+  private static void writeNewCodeDefinitions(JsonWriter json, TelemetryData telemetryData) {
+    if (telemetryData.getNewCodeDefinitions() != null) {
+      json.name("new-code-definitions");
+      json.beginArray();
+      telemetryData.getNewCodeDefinitions().forEach(ncd -> {
+        json.beginObject();
+        json.prop(NCD_ID, ncd.hashCode());
+        json.prop("type", ncd.type());
+        json.prop("value", ncd.value());
+        json.prop("scope", ncd.scope());
+        json.endObject();
+      });
+      json.endArray();
+    }
+  }
+
   private static void writeProjectStatsData(JsonWriter json, TelemetryData telemetryData) {
     if (telemetryData.getProjectStatistics() != null) {
       json.name("projects-general-stats");
       json.beginArray();
       telemetryData.getProjectStatistics().forEach(project -> {
         json.beginObject();
-        json.prop("projectUuid", project.getProjectUuid());
+        json.prop(PROJECT_ID, project.getProjectUuid());
         json.prop("branchCount", project.getBranchCount());
         json.prop("pullRequestCount", project.getPullRequestCount());
         json.prop("qualityGate", project.getQualityGate());
         json.prop("scm", project.getScm());
         json.prop("ci", project.getCi());
         json.prop("devopsPlatform", project.getDevopsPlatform());
+        json.prop(NCD_ID, project.getNcdId());
         project.getBugs().ifPresent(bugs -> json.prop("bugs", bugs));
         project.getVulnerabilities().ifPresent(vulnerabilities -> json.prop("vulnerabilities", vulnerabilities));
         project.getSecurityHotspots().ifPresent(securityHotspots -> json.prop("securityHotspots", securityHotspots));
index db1ca09b9265555143727584e19e65c15805a696..d496161c39d6f5276edceac25aa1ea001ebb2d3b 100644 (file)
@@ -23,4 +23,6 @@ public interface TelemetryDataLoader {
   TelemetryData load();
 
   String loadServerId();
+
+  void reset();
 }
index 3089a4da071dc03c8e6e3061c45bceba0d3898a3..7771dd78b19720d1c83fbcdf4062dd3cc33a4366 100644 (file)
@@ -48,6 +48,8 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
 import static org.sonar.test.JsonAssert.assertJson;
 
 @RunWith(DataProviderRunner.class)
@@ -61,6 +63,13 @@ public class TelemetryDataJsonWriterTest {
 
   private final TelemetryDataJsonWriter underTest = new TelemetryDataJsonWriter(List.of(extension), system2);
 
+  private static final int NCD_ID = 12345;
+
+  private static final TelemetryData.NewCodeDefinition NCD_INSTANCE =
+    new TelemetryData.NewCodeDefinition(PREVIOUS_VERSION.name(), "", "instance");
+  private static final TelemetryData.NewCodeDefinition NCD_PROJECT =
+    new TelemetryData.NewCodeDefinition(NUMBER_OF_DAYS.name(), "30", "project");
+
   @Test
   public void write_server_id_version_and_sequence() {
     TelemetryData data = telemetryBuilder().build();
@@ -414,7 +423,8 @@ public class TelemetryDataJsonWriterTest {
             "vulnerabilities": 3,
             "securityHotspots": 4,
             "technicalDebt": 60,
-            "developmentCost": 30
+            "developmentCost": 30,
+            "ncdId": 12345
           },
           {
             "projectUuid": "uuid-1",
@@ -428,7 +438,8 @@ public class TelemetryDataJsonWriterTest {
             "vulnerabilities": 6,
             "securityHotspots": 8,
             "technicalDebt": 120,
-            "developmentCost": 60
+            "developmentCost": 60,
+            "ncdId": 12345
           },
           {
             "projectUuid": "uuid-2",
@@ -442,7 +453,8 @@ public class TelemetryDataJsonWriterTest {
             "vulnerabilities": 9,
             "securityHotspots": 12,
             "technicalDebt": 180,
-            "developmentCost": 90
+            "developmentCost": 90,
+            "ncdId": 12345
           }
         ]
       }
@@ -497,6 +509,70 @@ public class TelemetryDataJsonWriterTest {
     );
   }
 
+  @Test
+  public void writes_all_branches() {
+    TelemetryData data = telemetryBuilder()
+      .setBranches(attachBranches())
+      .build();
+
+    String json = writeTelemetryData(data);
+    assertJson(json).isSimilarTo("""
+      {
+        "branches": [
+          {
+            "projectUuid": "%s",
+            "branchUuid": "%s",
+            "ncdId": %s
+          },
+          {
+            "projectUuid": "%s",
+            "branchUuid": "%s",
+            "ncdId": %s
+          },
+        ]
+
+      }
+      """.formatted("projectUuid1", "branchUuid1", NCD_ID, "projectUuid2", "branchUuid2", NCD_ID));
+  }
+
+  @Test
+  public void writes_new_code_definitions() {
+    TelemetryData data = telemetryBuilder()
+      .setNewCodeDefinitions(attachNewCodeDefinitions())
+      .build();
+
+    String json = writeTelemetryData(data);
+    assertJson(json).isSimilarTo("""
+      {
+        "new-code-definitions": [
+          {
+            "ncdId": %s,
+            "type": "%s",
+            "value": "%s",
+            "scope": "%s"
+          },
+          {
+            "ncdId": %s,
+            "type": "%s",
+            "value": "%s",
+            "scope": "%s"
+          },
+        ]
+
+      }
+      """.formatted(NCD_INSTANCE.hashCode(), NCD_INSTANCE.type(), NCD_INSTANCE.value(), NCD_INSTANCE.scope(), NCD_PROJECT.hashCode(),
+      NCD_PROJECT.type(), NCD_PROJECT.value(), NCD_PROJECT.scope()));
+  }
+
+  @Test
+  public void writes_instance_new_code_definition() {
+    TelemetryData data = telemetryBuilder().build();
+
+    String json = writeTelemetryData(data);
+    assertThat(json).contains("ncdId");
+
+  }
+
   private static TelemetryData.Builder telemetryBuilder() {
     return TelemetryData.builder()
       .setServerId("foo")
@@ -504,7 +580,8 @@ public class TelemetryDataJsonWriterTest {
       .setMessageSequenceNumber(1L)
       .setPlugins(Collections.emptyMap())
       .setManagedInstanceInformation(new TelemetryData.ManagedInstanceInformation(false, null))
-      .setDatabase(new TelemetryData.Database("H2", "11"));
+      .setDatabase(new TelemetryData.Database("H2", "11"))
+      .setNcdId(NCD_ID);
   }
 
   @NotNull
@@ -535,7 +612,8 @@ public class TelemetryDataJsonWriterTest {
       .setPRCount((i + 1L) * 2L)
       .setQG("qg-" + i).setCi("ci-" + i)
       .setScm("scm-" + i)
-      .setDevops("devops-" + i);
+      .setDevops("devops-" + i)
+      .setNcdId(NCD_ID);
   }
 
   private static TelemetryData.ProjectStatistics.Builder getProjectStatisticsWithMetricBuilder(int i) {
@@ -553,6 +631,14 @@ public class TelemetryDataJsonWriterTest {
       new TelemetryData.QualityGate("uuid-2", "over-compliant"));
   }
 
+  private List<TelemetryData.Branch> attachBranches() {
+    return List.of(new TelemetryData.Branch("projectUuid1", "branchUuid1", NCD_ID),
+      new TelemetryData.Branch("projectUuid2", "branchUuid2", NCD_ID));
+  }
+  private List<TelemetryData.NewCodeDefinition> attachNewCodeDefinitions() {
+    return List.of(NCD_INSTANCE, NCD_PROJECT);
+  }
+
   @DataProvider
   public static Object[][] allEditions() {
     return Arrays.stream(EditionProvider.Edition.values())
index 974219952bac0950e190f13be6969210dd8b62e5..fe9ad60ea117aae40d4b814f2f0229648fcb4cd6 100644 (file)
@@ -150,6 +150,7 @@ public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceIm
       dataJsonWriter.writeTelemetryData(json, statistics);
     }
     telemetryClient.upload(jsonString.toString());
+    dataLoader.reset();
   }
 
   private boolean shouldUploadStatistics(long now) {
index 0d87f2b9e0b32735295153627d0a4591dfca2721..03c441fe5b7f82e6d393e975330e4e3ec4199f89 100644 (file)
@@ -27,12 +27,14 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 import javax.inject.Inject;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.platform.Server;
@@ -46,11 +48,13 @@ import org.sonar.db.DbSession;
 import org.sonar.db.alm.setting.ALM;
 import org.sonar.db.alm.setting.ProjectAlmKeyAndProject;
 import org.sonar.db.component.AnalysisPropertyValuePerProject;
+import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.PrBranchAnalyzedLanguageCountByProjectDto;
 import org.sonar.db.component.SnapshotDto;
 import org.sonar.db.measure.LiveMeasureDto;
 import org.sonar.db.measure.ProjectLocDistributionDto;
 import org.sonar.db.metric.MetricDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodDto;
 import org.sonar.db.qualitygate.ProjectQgateAssociationDto;
 import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.server.management.ManagedInstanceService;
@@ -59,6 +63,7 @@ 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 org.sonar.server.telemetry.TelemetryData.NewCodeDefinition;
 
 import static java.util.Arrays.asList;
 import static java.util.Optional.ofNullable;
@@ -77,6 +82,7 @@ import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETEC
 import static org.sonar.core.platform.EditionProvider.Edition.COMMUNITY;
 import static org.sonar.core.platform.EditionProvider.Edition.DATACENTER;
 import static org.sonar.core.platform.EditionProvider.Edition.ENTERPRISE;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
 import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_KEY;
 import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY;
 import static org.sonar.server.telemetry.TelemetryDaemon.I_PROP_MESSAGE_SEQUENCE;
@@ -103,6 +109,10 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   private final QualityGateCaycChecker qualityGateCaycChecker;
   private final QualityGateFinder qualityGateFinder;
   private final ManagedInstanceService managedInstanceService;
+  private final Set<NewCodeDefinition> newCodeDefinitions = new HashSet<>();
+  private final Map<String, NewCodeDefinition> ncdByProject = new HashMap<>();
+  private final Map<String, NewCodeDefinition> ncdByBranch = new HashMap<>();
+  private NewCodeDefinition instanceNcd = NewCodeDefinition.getInstanceDefault();
 
   @Inject
   public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository,
@@ -143,7 +153,12 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
       getVersion));
     data.setPlugins(plugins);
     try (DbSession dbSession = dbClient.openSession(false)) {
+      var branchDtos = dbClient.branchDao().selectAllBranches(dbSession);
+      loadNewCodeDefinitions(dbSession, branchDtos);
+
       data.setDatabase(loadDatabaseMetadata(dbSession));
+      data.setNcdId(instanceNcd.hashCode());
+      data.setNewCodeDefinitions(newCodeDefinitions);
 
       String defaultQualityGateUuid = qualityGateFinder.getDefault(dbSession).getUuid();
 
@@ -151,6 +166,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
       resolveUnanalyzedLanguageCode(data, dbSession);
       resolveProjectStatistics(data, dbSession, defaultQualityGateUuid);
       resolveProjects(data, dbSession);
+      resolveBranches(data, branchDtos);
       resolveQualityGates(data, dbSession);
       resolveUsers(data, dbSession);
     }
@@ -161,7 +177,6 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     installationDateProperty.ifPresent(s -> data.setInstallationDate(Long.valueOf(s)));
     Optional<String> installationVersionProperty = internalProperties.read(InternalProperties.INSTALLATION_VERSION);
 
-
     return data
       .setInstallationVersion(installationVersionProperty.orElse(null))
       .setInDocker(dockerSupport.isRunningInDocker())
@@ -169,6 +184,59 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
       .build();
   }
 
+  private void resolveBranches(TelemetryData.Builder data, List<BranchDto> branchDtos) {
+    var branches = branchDtos.stream()
+      .map(dto -> {
+        var projectNcd = ncdByProject.getOrDefault(dto.getProjectUuid(), instanceNcd);
+        var ncdId = ncdByBranch.getOrDefault(dto.getUuid(), projectNcd).hashCode();
+        return new TelemetryData.Branch(dto.getProjectUuid(), dto.getUuid(), ncdId);
+      })
+      .toList();
+    data.setBranches(branches);
+  }
+
+  @Override
+  public void reset() {
+    this.newCodeDefinitions.clear();
+    this.ncdByBranch.clear();
+    this.ncdByProject.clear();
+    this.instanceNcd = NewCodeDefinition.getInstanceDefault();
+  }
+
+  private void loadNewCodeDefinitions(DbSession dbSession, List<BranchDto> branchDtos) {
+    var branchUuidByKey = branchDtos.stream().collect(Collectors.toMap(dto -> createBranchUniqueKey(dto.getProjectUuid(), dto.getBranchKey()), BranchDto::getUuid));
+    List<NewCodePeriodDto> newCodePeriodDtos = dbClient.newCodePeriodDao().selectAll(dbSession);
+    NewCodeDefinition ncd;
+    boolean hasInstance = false;
+    for (var dto : newCodePeriodDtos) {
+      String projectUuid = dto.getProjectUuid();
+      String branchUuid = dto.getBranchUuid();
+      if (branchUuid == null && projectUuid == null) {
+        ncd = new NewCodeDefinition(dto.getType().name(), dto.getValue(), "instance");
+        this.instanceNcd = ncd;
+        hasInstance = true;
+      } else if (projectUuid != null) {
+        var value = dto.getType() == REFERENCE_BRANCH ? branchUuidByKey.get(createBranchUniqueKey(projectUuid, dto.getValue())) : dto.getValue();
+        if (branchUuid == null) {
+          ncd = new NewCodeDefinition(dto.getType().name(), value, "project");
+          this.ncdByProject.put(projectUuid, ncd);
+        } else {
+          ncd = new NewCodeDefinition(dto.getType().name(), value, "branch");
+          this.ncdByBranch.put(branchUuid, ncd);
+        }
+      } else {
+        throw new IllegalStateException(String.format("Error in loading telemetry data. New code definition for branch %s doesn't have a projectUuid", branchUuid));
+      }
+      this.newCodeDefinitions.add(ncd);
+    }
+    if (!hasInstance) {
+      this.newCodeDefinitions.add(NewCodeDefinition.getInstanceDefault());
+    }
+  }
+
+  private static String createBranchUniqueKey(String projectUuid, @Nullable String branchKey) {
+    return projectUuid + "-" + branchKey;
+  }
 
   private void resolveUnanalyzedLanguageCode(TelemetryData.Builder data, DbSession dbSession) {
     long numberOfUnanalyzedCMeasures = dbClient.liveMeasureDao().countProjectsHavingMeasure(dbSession, UNANALYZED_C_KEY);
@@ -216,6 +284,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
         .setVulnerabilities(metrics.getOrDefault("vulnerabilities", null))
         .setSecurityHotspots(metrics.getOrDefault("security_hotspots", null))
         .setTechnicalDebt(metrics.getOrDefault("sqale_index", null))
+        .setNcdId(ncdByProject.getOrDefault(projectUuid, instanceNcd).hashCode())
         .build();
       projectStatistics.add(stats);
     }
@@ -361,9 +430,6 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     return configuration.get(property).isPresent();
   }
 
-  private boolean isScimEnabled() {
-    return this.internalProperties.read(SCIM_PROPERTY_ENABLED).map(Boolean::parseBoolean).orElse(false);
-  }
 
   private TelemetryData.ManagedInstanceInformation buildManagedInstanceInformation() {
     String provider = managedInstanceService.isInstanceExternallyManaged() ? managedInstanceService.getProviderName() : null;
index 2018cd1095cf08f9065b7aa06c86a3cef525a30c..0df240825c3ce9cdc6abe2bee8e15849afafa182 100644 (file)
@@ -48,6 +48,8 @@ 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.newcodeperiod.NewCodePeriodDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
 import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.db.user.UserDbTester;
 import org.sonar.db.user.UserDto;
@@ -58,6 +60,8 @@ 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.telemetry.TelemetryData.NewCodeDefinition;
+import org.sonar.server.telemetry.TelemetryData.ProjectStatistics;
 import org.sonar.updatecenter.common.Version;
 
 import static java.util.Arrays.asList;
@@ -107,9 +111,9 @@ public class TelemetryDataLoaderImplTest {
   private final ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class);
 
   private final TelemetryDataLoader communityUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
-      internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService);
+    internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService);
   private final TelemetryDataLoader commercialUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
-      internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService);
+    internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService);
 
   private QualityGateDto builtInDefaultQualityGate;
   private MetricDto bugsDto;
@@ -199,11 +203,22 @@ public class TelemetryDataLoaderImplTest {
     // link one project to a non-default QG
     db.qualityGates().associateProjectToQualityGate(db.components().getProjectDtoByMainBranch(project1), qualityGate1);
 
+    var branch1 = db.components().insertProjectBranch(project1, branchDto -> branchDto.setKey("reference"));
+    var branch2 = db.components().insertProjectBranch(project1, branchDto -> branchDto.setKey("custom"));
+
+    var ncd1 = db.newCodePeriods().insert(project1.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "30");
+    var ncd2 = db.newCodePeriods().insert(project1.uuid(), branch2.branchUuid(), NewCodePeriodType.REFERENCE_BRANCH, "reference");
+
+    var instanceNcdId = NewCodeDefinition.getInstanceDefault().hashCode();
+    var projectNcdId = new NewCodeDefinition(NewCodePeriodType.NUMBER_OF_DAYS.name(), "30", "project").hashCode();
+    var branchNcdId = new NewCodeDefinition(NewCodePeriodType.REFERENCE_BRANCH.name(), branch1.uuid(), "branch").hashCode();
+
     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.getNcdId()).isEqualTo(NewCodeDefinition.getInstanceDefault().hashCode());
     assertThat(data.getMessageSequenceNumber()).isOne();
     assertDatabaseMetadata(data.getDatabase());
     assertThat(data.getPlugins()).containsOnly(
@@ -226,13 +241,31 @@ public class TelemetryDataLoaderImplTest {
         tuple(project2.uuid(), "java", 180L, analysisDate),
         tuple(project2.uuid(), "js", 20L, analysisDate));
     assertThat(data.getProjectStatistics())
-      .extracting(TelemetryData.ProjectStatistics::getBranchCount, TelemetryData.ProjectStatistics::getPullRequestCount, TelemetryData.ProjectStatistics::getQualityGate,
-        TelemetryData.ProjectStatistics::getScm, TelemetryData.ProjectStatistics::getCi, TelemetryData.ProjectStatistics::getDevopsPlatform,
-        TelemetryData.ProjectStatistics::getBugs, TelemetryData.ProjectStatistics::getVulnerabilities, TelemetryData.ProjectStatistics::getSecurityHotspots,
-        TelemetryData.ProjectStatistics::getDevelopmentCost, TelemetryData.ProjectStatistics::getTechnicalDebt)
+      .extracting(ProjectStatistics::getBranchCount, ProjectStatistics::getPullRequestCount, ProjectStatistics::getQualityGate,
+        ProjectStatistics::getScm, ProjectStatistics::getCi, ProjectStatistics::getDevopsPlatform,
+        ProjectStatistics::getBugs, ProjectStatistics::getVulnerabilities, ProjectStatistics::getSecurityHotspots,
+        ProjectStatistics::getDevelopmentCost, ProjectStatistics::getTechnicalDebt, ProjectStatistics::getNcdId)
+      .containsExactlyInAnyOrder(
+        tuple(3L, 0L, qualityGate1.getUuid(), "scm-1", "ci-1", "azure_devops_cloud", Optional.of(1L), Optional.of(1L), Optional.of(1L), Optional.of(50L), Optional.of(5L),
+          projectNcdId),
+        tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud", Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
+          Optional.empty(), instanceNcdId));
+
+    assertThat(data.getBranches())
+      .extracting(TelemetryData.Branch::branchUuid, TelemetryData.Branch::ncdId)
+      .containsExactlyInAnyOrder(
+        tuple(branch1.uuid(), projectNcdId),
+        tuple(branch2.uuid(), branchNcdId),
+        tuple(project1.uuid(), projectNcdId),
+        tuple(project2.uuid(), instanceNcdId));
+
+    assertThat(data.getNewCodeDefinitions())
+      .extracting(NewCodeDefinition::scope, NewCodeDefinition::type, NewCodeDefinition::value)
       .containsExactlyInAnyOrder(
-        tuple(1L, 0L, qualityGate1.getUuid(), "scm-1", "ci-1", "azure_devops_cloud", Optional.of(1L), Optional.of(1L), Optional.of(1L), Optional.of(50L), Optional.of(5L)),
-        tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud", Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()));
+        tuple("instance", NewCodePeriodType.PREVIOUS_VERSION.name(), ""),
+        tuple("project", NewCodePeriodType.NUMBER_OF_DAYS.name(), "30"),
+        tuple("branch", NewCodePeriodType.REFERENCE_BRANCH.name(), branch1.uuid()));
+
     assertThat(data.getQualityGates())
       .extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::caycStatus)
       .containsExactlyInAnyOrder(
@@ -297,8 +330,8 @@ public class TelemetryDataLoaderImplTest {
         tuple(project.uuid(), "js", 50L),
         tuple(project.uuid(), "kotlin", 30L));
     assertThat(data.getProjectStatistics())
-      .extracting(TelemetryData.ProjectStatistics::getBranchCount, TelemetryData.ProjectStatistics::getPullRequestCount,
-        TelemetryData.ProjectStatistics::getScm, TelemetryData.ProjectStatistics::getCi)
+      .extracting(ProjectStatistics::getBranchCount, ProjectStatistics::getPullRequestCount,
+        ProjectStatistics::getScm, ProjectStatistics::getCi)
       .containsExactlyInAnyOrder(
         tuple(2L, 0L, "undetected", "undetected"));
   }
@@ -436,7 +469,7 @@ public class TelemetryDataLoaderImplTest {
     db.components().insertPublicProject().getMainBranchComponent();
     TelemetryData data = communityUnderTest.load();
     assertThat(data.getProjectStatistics())
-      .extracting(TelemetryData.ProjectStatistics::getDevopsPlatform, TelemetryData.ProjectStatistics::getScm, TelemetryData.ProjectStatistics::getCi)
+      .extracting(ProjectStatistics::getDevopsPlatform, ProjectStatistics::getScm, ProjectStatistics::getCi)
       .containsExactlyInAnyOrder(tuple("undetected", "undetected", "undetected"));
   }
 
@@ -460,7 +493,7 @@ public class TelemetryDataLoaderImplTest {
     db.qualityGates().setDefaultQualityGate(qualityGate);
     TelemetryData data = communityUnderTest.load();
     assertThat(data.getProjectStatistics())
-      .extracting(TelemetryData.ProjectStatistics::getQualityGate)
+      .extracting(ProjectStatistics::getQualityGate)
       .containsOnly(qualityGate.getUuid());
   }
 
@@ -489,7 +522,7 @@ public class TelemetryDataLoaderImplTest {
 
   @DataProvider
   public static Object[][] getManagedInstanceData() {
-    return new Object[][]{
+    return new Object[][] {
       {true, "scim"},
       {true, "github"},
       {true, "gitlab"},