]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14525 include SCM usage information in telemetry
authorMichal Duda <michal.duda@sonarsource.com>
Fri, 26 Feb 2021 14:03:43 +0000 (15:03 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 4 Mar 2021 20:12:49 +0000 (20:12 +0000)
17 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStep.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ProjectCountPerAnalysisPropertyValue.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/component/AnalysisPropertiesMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/component/AnalysisPropertiesDaoTest.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/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java
sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java

index aa74407325df948721407804ed2b91c87c117d6b..ec1055ff4b593c7d3f0c83a0f08f8ea15d80f2c7 100644 (file)
@@ -32,10 +32,10 @@ import org.sonar.db.component.AnalysisPropertyDto;
 import org.sonar.scanner.protocol.output.ScannerReport;
 
 import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS;
+import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDSCM;
 
 /**
  * Persist analysis properties
- * Only properties starting with "sonar.analysis" or "sonar.pullrequest" will be persisted in database
  */
 public class PersistAnalysisPropertiesStep implements ComputationStep {
 
@@ -47,7 +47,7 @@ public class PersistAnalysisPropertiesStep implements ComputationStep {
   private final UuidFactory uuidFactory;
 
   public PersistAnalysisPropertiesStep(DbClient dbClient, AnalysisMetadataHolder analysisMetadataHolder,
-    BatchReportReader reportReader, UuidFactory uuidFactory) {
+                                       BatchReportReader reportReader, UuidFactory uuidFactory) {
     this.dbClient = dbClient;
     this.analysisMetadataHolder = analysisMetadataHolder;
     this.reportReader = reportReader;
@@ -61,7 +61,7 @@ public class PersistAnalysisPropertiesStep implements ComputationStep {
       it.forEachRemaining(
         contextProperty -> {
           String propertyKey = contextProperty.getKey();
-          if (propertyKey.startsWith(SONAR_ANALYSIS) || propertyKey.startsWith(SONAR_PULL_REQUEST)) {
+          if (propertyKey.startsWith(SONAR_ANALYSIS) || propertyKey.startsWith(SONAR_PULL_REQUEST) || SONAR_ANALYSIS_DETECTEDSCM.equals(propertyKey)) {
             analysisPropertyDtos.add(new AnalysisPropertyDto()
               .setUuid(uuidFactory.create())
               .setKey(propertyKey)
index f25e7820ece036d12da7b175abee28051e3e7f26..582390378547edcb115d8bb35dbec07ccd291458 100644 (file)
@@ -54,6 +54,7 @@ import org.sonar.db.component.ComponentMapper;
 import org.sonar.db.component.ComponentWithModuleUuidDto;
 import org.sonar.db.component.FilePathWithHashDto;
 import org.sonar.db.component.KeyWithUuidDto;
+import org.sonar.db.component.ProjectCountPerAnalysisPropertyValue;
 import org.sonar.db.component.ProjectLinkMapper;
 import org.sonar.db.component.ResourceDto;
 import org.sonar.db.component.ScrapAnalysisPropertyDto;
@@ -197,6 +198,7 @@ public class MyBatis implements Startable {
     confBuilder.loadAlias("PrIssue", PrIssueDto.class);
     confBuilder.loadAlias("ProjectQgateAssociation", ProjectQgateAssociationDto.class);
     confBuilder.loadAlias("Project", ProjectDto.class);
+    confBuilder.loadAlias("ProjectCountPerAnalysisPropertyValue", ProjectCountPerAnalysisPropertyValue.class);
     confBuilder.loadAlias("ProjectMapping", ProjectMappingDto.class);
     confBuilder.loadAlias("PurgeableAnalysis", PurgeableAnalysisDto.class);
     confBuilder.loadAlias("QualityGateCondition", QualityGateConditionDto.class);
index 83c5698b07f8875fd6f3535a9531d3d98bd2b4ec..c40ad31952fe6d05808e46f6e84287dcb0e6f51c 100644 (file)
@@ -63,6 +63,10 @@ public class AnalysisPropertiesDao implements Dao {
     }
   }
 
+  public List<ProjectCountPerAnalysisPropertyValue> selectProjectCountPerAnalysisPropertyValueInLastAnalysis(DbSession session, String analysisPropertyKey) {
+    return getMapper(session).selectProjectCountPerAnalysisPropertyValueInLastAnalysis(analysisPropertyKey);
+  }
+
   private static boolean mustBeStoredInClob(String value) {
     return value.length() > VARCHAR_MAXSIZE;
   }
index fef92d58c067e23fbbcaec34814cd08c106aee81..19080f1413b2cfe043f95d3cb7dde4ebe3a4bddd 100644 (file)
@@ -31,4 +31,6 @@ public interface AnalysisPropertiesMapper {
   void insertAsClob(@Param("analysisPropertyDto") AnalysisPropertyDto analysisPropertyDto, @Param("createdAt") long createdAt);
 
   void insertAsText(@Param("analysisPropertyDto") AnalysisPropertyDto analysisPropertyDto, @Param("createdAt") long createdAt);
+
+  List<ProjectCountPerAnalysisPropertyValue> selectProjectCountPerAnalysisPropertyValueInLastAnalysis(@Param("analysisPropertyKey") String analysisPropertyKey);
 }
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ProjectCountPerAnalysisPropertyValue.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ProjectCountPerAnalysisPropertyValue.java
new file mode 100644 (file)
index 0000000..bdc7055
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.component;
+
+public class ProjectCountPerAnalysisPropertyValue {
+  private String propertyValue;
+  private Long count;
+
+  public ProjectCountPerAnalysisPropertyValue() {
+    //nothing to do here
+  }
+
+  public String getPropertyValue() {
+    return propertyValue;
+  }
+
+  public void setPropertyValue(String propertyValue) {
+    this.propertyValue = propertyValue;
+  }
+
+  public Long getCount() {
+    return count;
+  }
+
+  public void setCount(Long count) {
+    this.count = count;
+  }
+}
index 865f072c2b1bceb5a4250322a2a6c482f53e5ad0..124b27aad3d56dded39d83cccad6de34bf5f1c1b 100644 (file)
       analysis_uuid = #{analysisUuid}
   </select>
 
+  <select id="selectProjectCountPerAnalysisPropertyValueInLastAnalysis" parameterType="string" resultType="ProjectCountPerAnalysisPropertyValue">
+    select
+      ap.text_value as "propertyValue",
+      count(ap.text_value) as "count"
+    from components cp
+    inner join snapshots s on s.component_uuid = cp.uuid
+    inner join analysis_properties ap on ap.analysis_uuid = s.uuid
+    where
+      s.islast = ${_true} and ap.kee = #{analysisPropertyKey, jdbcType=VARCHAR}
+    group by ap.text_value
+  </select>
+
   <insert id="insertAsEmpty" parameterType="map" useGeneratedKeys="false">
     INSERT INTO analysis_properties (
       uuid,
index 616aa844389d7b0c65a8781c7188ef7511b74de2..9d49dcecb892d93c641eca30944aa7836a65279a 100644 (file)
@@ -25,13 +25,15 @@ import java.util.Random;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.sonar.api.utils.System2;
 import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.System2;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.project.ProjectDto;
 
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.tuple;
 
 public class AnalysisPropertiesDaoTest {
   private static final long NOW = 1_000L;
@@ -159,6 +161,32 @@ public class AnalysisPropertiesDaoTest {
     assertThat(result).containsExactlyInAnyOrder(propertyDtos.toArray(new AnalysisPropertyDto[0]));
   }
 
+  @Test
+  public void selectProjectCountPerAnalysisPropertyValueInLastAnalysis_should_return_correct_values() {
+    final String analysisPropertyKey = "key";
+    for (int i = 0; i < 7; i++) {
+      final int index = i;
+      ProjectDto project = dbTester.components().insertPrivateProjectDto();
+      dbTester.components().insertSnapshot(project, s -> s.setLast(true).setUuid("uuid" + index));
+    }
+    underTest.insert(dbSession, new AnalysisPropertyDto().setKey(analysisPropertyKey).setValue("git").setAnalysisUuid("uuid0").setUuid("0"));
+    underTest.insert(dbSession, new AnalysisPropertyDto().setKey(analysisPropertyKey).setValue("svn").setAnalysisUuid("uuid1").setUuid("1"));
+    underTest.insert(dbSession, new AnalysisPropertyDto().setKey(analysisPropertyKey).setValue("undetected").setAnalysisUuid("uuid2").setUuid("2"));
+    underTest.insert(dbSession, new AnalysisPropertyDto().setKey(analysisPropertyKey).setValue("undetected").setAnalysisUuid("uuid3").setUuid("3"));
+    underTest.insert(dbSession, new AnalysisPropertyDto().setKey(analysisPropertyKey).setValue("git").setAnalysisUuid("uuid4").setUuid("4"));
+    underTest.insert(dbSession, new AnalysisPropertyDto().setKey(analysisPropertyKey).setValue("git").setAnalysisUuid("uuid5").setUuid("5"));
+
+    List<ProjectCountPerAnalysisPropertyValue> result = underTest.selectProjectCountPerAnalysisPropertyValueInLastAnalysis(dbSession, analysisPropertyKey);
+
+    assertThat(result)
+      .extracting(ProjectCountPerAnalysisPropertyValue::getPropertyValue, ProjectCountPerAnalysisPropertyValue::getCount)
+      .containsExactlyInAnyOrder(
+        tuple("git", 3L),
+        tuple("svn", 1L),
+        tuple("undetected", 2L)
+      );
+  }
+
   private AnalysisPropertyDto insertAnalysisPropertyDto(int valueLength) {
     AnalysisPropertyDto analysisPropertyDto = newAnalysisPropertyDto(valueLength, randomAlphanumeric(40));
     underTest.insert(dbSession, analysisPropertyDto);
@@ -171,7 +199,7 @@ public class AnalysisPropertiesDaoTest {
       .setKey(randomAlphanumeric(512))
       .setUuid(randomAlphanumeric(40))
       .setValue(randomAlphanumeric(valueLength))
-      .setCreatedAt( 1_000L);
+      .setCreatedAt(1_000L);
   }
 
   private void compareFirstValueWith(AnalysisPropertyDto analysisPropertyDto) {
index 4710e3f9d75fd520f458b1921adfa0c3bb81d2b2..b625d1aead5495bbd1bb8b79dac7ca6cd5f87633 100644 (file)
@@ -43,6 +43,7 @@ public class TelemetryData {
   private final Map<String, Long> almIntegrationCountByAlm;
   private final Map<String, Long> nclocByLanguage;
   private final List<String> externalAuthenticationProviders;
+  private final Map<String, Long> projectCountByScm;
   private final EditionProvider.Edition edition;
   private final String licenseType;
   private final Long installationDate;
@@ -73,6 +74,7 @@ public class TelemetryData {
     hasUnanalyzedCpp = builder.hasUnanalyzedCpp;
     customSecurityConfigs = builder.customSecurityConfigs == null ? emptyList() : builder.customSecurityConfigs;
     externalAuthenticationProviders = builder.externalAuthenticationProviders;
+    projectCountByScm = builder.projectCountByScm;
   }
 
   public String getServerId() {
@@ -155,6 +157,10 @@ public class TelemetryData {
     return externalAuthenticationProviders;
   }
 
+  public Map<String, Long> getProjectCountByScm() {
+    return projectCountByScm;
+  }
+
   static Builder builder() {
     return new Builder();
   }
@@ -178,6 +184,8 @@ public class TelemetryData {
     private Boolean hasUnanalyzedCpp;
     private List<String> customSecurityConfigs;
     private List<String> externalAuthenticationProviders;
+    private Map<String, Long> projectCountByScm;
+
 
     private Builder() {
       // enforce static factory method
@@ -188,6 +196,11 @@ public class TelemetryData {
       return this;
     }
 
+    Builder setProjectCountByScm(Map<String, Long> projectCountByScm) {
+      this.projectCountByScm = projectCountByScm;
+      return this;
+    }
+
     Builder setServerId(String serverId) {
       this.serverId = serverId;
       return this;
@@ -283,6 +296,7 @@ public class TelemetryData {
       requireNonNull(database);
       requireNonNull(usingBranches);
       requireNonNull(externalAuthenticationProviders);
+      requireNonNull(projectCountByScm);
 
       return new TelemetryData(this);
     }
index 17366c51ebedcb64683ff01eb1d5f7017a3aa6d7..e675e1dc101c47a29225eeac60552f9283c00319 100644 (file)
@@ -26,6 +26,8 @@ import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
 
 public class TelemetryDataJsonWriter {
 
+  public static final String COUNT = "count";
+
   public void writeTelemetryData(JsonWriter json, TelemetryData statistics) {
     json.beginObject();
     json.prop("id", statistics.getServerId());
@@ -55,7 +57,7 @@ public class TelemetryDataJsonWriter {
     statistics.getProjectCountByLanguage().forEach((language, count) -> {
       json.beginObject();
       json.prop("language", language);
-      json.prop("count", count);
+      json.prop(COUNT, count);
       json.endObject();
     });
     json.endArray();
@@ -73,7 +75,7 @@ public class TelemetryDataJsonWriter {
     statistics.getAlmIntegrationCountByAlm().forEach((alm, count) -> {
       json.beginObject();
       json.prop("alm", alm);
-      json.prop("count", count);
+      json.prop(COUNT, count);
       json.endObject();
     });
     json.endArray();
@@ -93,6 +95,16 @@ public class TelemetryDataJsonWriter {
     statistics.getExternalAuthenticationProviders().forEach(json::value);
     json.endArray();
 
+    json.name("projectCountByScm");
+    json.beginArray();
+    statistics.getProjectCountByScm().forEach((scm, count) -> {
+      json.beginObject();
+      json.prop("scm", scm);
+      json.prop(COUNT, count);
+      json.endObject();
+    });
+    json.endArray();
+
     if (statistics.getInstallationDate() != null) {
       json.prop("installationDate", statistics.getInstallationDate());
     }
index 141e4b5719876f66f5df21bccb9343a6caa5a29b..ca3e14f774cef2e8b4127c8c16170df553877f7b 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.core.platform.EditionProvider;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.server.measure.index.ProjectMeasuresStatistics;
 
+import static java.util.Arrays.asList;
 import static java.util.stream.Collectors.joining;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -56,7 +57,8 @@ public class TelemetryDataJsonWriterTest {
       .setNclocByLanguage(Collections.emptyMap())
       .build())
     .setNcloc(42L)
-    .setExternalAuthenticationProviders(Arrays.asList("github", "gitlab"))
+    .setExternalAuthenticationProviders(asList("github", "gitlab"))
+    .setProjectCountByScm(Collections.emptyMap())
     .setDatabase(new TelemetryData.Database("H2", "11"))
     .setUsingBranches(true);
 
@@ -215,6 +217,23 @@ public class TelemetryDataJsonWriterTest {
       "}");
   }
 
+  @Test
+  public void write_project_count_by_scm() {
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setProjectCountByScm(ImmutableMap.of("git", 5L, "svn", 4L, "cvs", 3L, "undetected", 2L))
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"projectCountByScm\": ["
+      + "{ \"scm\":\"git\", \"count\":5},"
+      + "{ \"scm\":\"svn\", \"count\":4},"
+      + "{ \"scm\":\"cvs\", \"count\":3},"
+      + "{ \"scm\":\"undetected\", \"count\":2},"
+      + "]}");
+  }
+
   @Test
   public void write_project_stats_by_language() {
     int projectCount = random.nextInt(8909);
@@ -368,7 +387,7 @@ public class TelemetryDataJsonWriterTest {
   @DataProvider
   public static Object[][] allEditions() {
     return Arrays.stream(EditionProvider.Edition.values())
-      .map(t -> new Object[] {t})
+      .map(t -> new Object[]{t})
       .toArray(Object[][]::new);
   }
 
index 266a6db942a468e5df4f7ac53f35c027382f7f31..c944b32e8577c2903157d472d933dff679fb4efe 100644 (file)
@@ -32,6 +32,7 @@ import javax.annotation.Nullable;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.platform.Server;
 import org.sonar.api.server.ServerSide;
+import org.sonar.core.config.CorePropertyDefinitions;
 import org.sonar.core.platform.PlatformEditionProvider;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.platform.PluginRepository;
@@ -40,6 +41,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.alm.setting.ALM;
 import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.db.component.ProjectCountPerAnalysisPropertyValue;
 import org.sonar.db.measure.SumNclocDbQuery;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.measure.index.ProjectMeasuresIndex;
@@ -74,13 +76,13 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   private final LicenseReader licenseReader;
 
   public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository, UserIndex userIndex, ProjectMeasuresIndex projectMeasuresIndex,
-    PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration, DockerSupport dockerSupport) {
+                                 PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration, DockerSupport dockerSupport) {
     this(server, dbClient, pluginRepository, userIndex, projectMeasuresIndex, editionProvider, internalProperties, configuration, dockerSupport, null);
   }
 
   public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository, UserIndex userIndex, ProjectMeasuresIndex projectMeasuresIndex,
-    PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration,
-    DockerSupport dockerSupport, @Nullable LicenseReader licenseReader) {
+                                 PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration,
+                                 DockerSupport dockerSupport, @Nullable LicenseReader licenseReader) {
     this.server = server;
     this.dbClient = dbClient;
     this.pluginRepository = pluginRepository;
@@ -137,7 +139,11 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
 
       data.setAlmIntegrationCountByAlm(countAlmUsage(dbSession));
       data.setExternalAuthenticationProviders(dbClient.userDao().selectExternalIdentityProviders(dbSession));
-
+      Map<String, Long> projectCountPerScmDetected = dbClient.analysisPropertiesDao()
+        .selectProjectCountPerAnalysisPropertyValueInLastAnalysis(dbSession, CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDSCM)
+        .stream()
+        .collect(Collectors.toMap(ProjectCountPerAnalysisPropertyValue::getPropertyValue, ProjectCountPerAnalysisPropertyValue::getCount));
+      data.setProjectCountByScm(projectCountPerScmDetected);
     }
 
     setSecurityCustomConfigIfPresent(data);
index f3096ecfe32b38d70a9bfb4c57514448b9330673..560e4461f19cdb7489861e1c70c8e831e1a6e1b3 100644 (file)
@@ -42,13 +42,13 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class ClusterSystemInfoWriterTest {
-  private GlobalInfoLoader globalInfoLoader = mock(GlobalInfoLoader.class);
-  private AppNodesInfoLoader appNodesInfoLoader = mock(AppNodesInfoLoader.class);
-  private SearchNodesInfoLoader searchNodesInfoLoader = mock(SearchNodesInfoLoader.class);
-  private HealthChecker healthChecker = mock(HealthChecker.class);
-  private TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
-  private TelemetryDataJsonWriter dataJsonWriter = new TelemetryDataJsonWriter();
-  private ClusterSystemInfoWriter underTest = new ClusterSystemInfoWriter(globalInfoLoader, appNodesInfoLoader,
+  private final GlobalInfoLoader globalInfoLoader = mock(GlobalInfoLoader.class);
+  private final AppNodesInfoLoader appNodesInfoLoader = mock(AppNodesInfoLoader.class);
+  private final SearchNodesInfoLoader searchNodesInfoLoader = mock(SearchNodesInfoLoader.class);
+  private final HealthChecker healthChecker = mock(HealthChecker.class);
+  private final TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
+  private final TelemetryDataJsonWriter dataJsonWriter = new TelemetryDataJsonWriter();
+  private final ClusterSystemInfoWriter underTest = new ClusterSystemInfoWriter(globalInfoLoader, appNodesInfoLoader,
     searchNodesInfoLoader, healthChecker, telemetry, dataJsonWriter);
 
   @Before
@@ -68,13 +68,13 @@ public class ClusterSystemInfoWriterTest {
     underTest.write(jsonWriter);
     jsonWriter.endObject();
 
-    assertThat(writer.toString()).isEqualTo("{\"Health\":\"GREEN\","
+    assertThat(writer).hasToString("{\"Health\":\"GREEN\","
       + "\"Health Causes\":[],\"\":{\"name\":\"globalInfo\"},"
       + "\"Application Nodes\":[{\"Name\":\"appNodes\",\"\":{\"name\":\"appNodes\"}}],"
       + "\"Search Nodes\":[{\"Name\":\"searchNodes\",\"\":{\"name\":\"searchNodes\"}}],"
       + "\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],"
       + "\"userCount\":0,\"projectCount\":0,\"usingBranches\":false,\"ncloc\":0,\"projectCountByLanguage\":[]," +
-      "\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
+      "\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
   }
 
   private static NodeInfo createNodeInfo(String name) {
index a7e99d4f40267f24b643e09b877e29ae5a32e37f..c22757e9e81f58d12942bb8b16a56d5111138f24 100644 (file)
@@ -48,14 +48,13 @@ public class StandaloneSystemInfoWriterTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private SystemInfoSection section1 = mock(SystemInfoSection.class);
-  private SystemInfoSection section2 = mock(SystemInfoSection.class);
-  private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS);
-  private TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
-  private TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
-  private TelemetryDataJsonWriter dataJsonWriter = new TelemetryDataJsonWriter();
-
-  private StandaloneSystemInfoWriter underTest = new StandaloneSystemInfoWriter(telemetry, ceHttpClient, healthChecker, dataJsonWriter, section1, section2);
+  private final SystemInfoSection section1 = mock(SystemInfoSection.class);
+  private final SystemInfoSection section2 = mock(SystemInfoSection.class);
+  private final CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS);
+  private final TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
+  private final TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
+  private final TelemetryDataJsonWriter dataJsonWriter = new TelemetryDataJsonWriter();
+  private final StandaloneSystemInfoWriter underTest = new StandaloneSystemInfoWriter(telemetry, ceHttpClient, healthChecker, dataJsonWriter, section1, section2);
 
   @Test
   public void write_json() {
@@ -79,10 +78,9 @@ public class StandaloneSystemInfoWriterTest {
     underTest.write(jsonWriter);
     jsonWriter.endObject();
     // response does not contain empty "Section Three"
-    assertThat(writer.toString()).isEqualTo("{\"Health\":\"GREEN\",\"Health Causes\":[],\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
+    assertThat(writer).hasToString("{\"Health\":\"GREEN\",\"Health Causes\":[],\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
       "\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],\"userCount\":0,\"projectCount\":0,\"usingBranches\":false," +
-      "\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"installationDate\":0," +
-      "\"installationVersion\":\"\",\"docker\":false}}");
+      "\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
   }
 
   private void logInAsSystemAdministrator() {
index 423c00cee027e5276326f939f726636b6fbe4f48..ccf32d6732760b92c5ab38108d387ff1c9d5354f 100644 (file)
@@ -35,6 +35,7 @@ import org.sonar.server.property.MapInternalProperties;
 import org.sonar.server.util.GlobalLockManager;
 import org.sonar.server.util.GlobalLockManagerImpl;
 
+import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -65,6 +66,8 @@ public class TelemetryDaemonTest {
     .setVersion("bar")
     .setPlugins(Collections.emptyMap())
     .setAlmIntegrationCountByAlm(Collections.emptyMap())
+    .setExternalAuthenticationProviders(singletonList("github"))
+    .setProjectCountByScm(Collections.emptyMap())
     .setProjectMeasuresStatistics(ProjectMeasuresStatistics.builder()
       .setProjectCount(12)
       .setProjectCountByLanguage(Collections.emptyMap())
@@ -76,15 +79,15 @@ public class TelemetryDaemonTest {
     .setUsingBranches(true)
     .build();
 
-  private TelemetryClient client = mock(TelemetryClient.class);
-  private InternalProperties internalProperties = spy(new MapInternalProperties());
+  private final TelemetryClient client = mock(TelemetryClient.class);
+  private final InternalProperties internalProperties = spy(new MapInternalProperties());
   private final GlobalLockManager lockManager = mock(GlobalLockManagerImpl.class);
-  private TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
-  private MapSettings settings = new MapSettings();
+  private final TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
+  private final MapSettings settings = new MapSettings();
 
   private final TelemetryDataLoader dataLoader = mock(TelemetryDataLoader.class);
   private final TelemetryDataJsonWriter dataJsonWriter = mock(TelemetryDataJsonWriter.class);
-  private TelemetryDaemon underTest = new TelemetryDaemon(dataLoader, dataJsonWriter, client, settings.asConfig(), internalProperties, lockManager, system2);
+  private final TelemetryDaemon underTest = new TelemetryDaemon(dataLoader, dataJsonWriter, client, settings.asConfig(), internalProperties, lockManager, system2);
 
   @After
   public void tearDown() {
@@ -134,7 +137,7 @@ public class TelemetryDaemonTest {
 
     internalProperties.write("telemetry.lastPing", String.valueOf(sevenDaysAgo));
 
-    verify(client,  timeout(2_000)).upload(anyString());
+    verify(client, timeout(2_000)).upload(anyString());
     verify(dataJsonWriter).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA));
   }
 
index 55167daa8c1f4b4871e63fdc2d722ffc94cfc8b5..58162ad28b6a81ef5b362c7034e72c004075f1f3 100644 (file)
@@ -34,6 +34,7 @@ import static org.sonar.api.PropertyType.STRING;
 public class CorePropertyDefinitions {
 
   public static final String SONAR_ANALYSIS = "sonar.analysis.";
+  public static final String SONAR_ANALYSIS_DETECTEDSCM = "sonar.analysis.detectedscm";
 
   private static final String CATEGORY_ORGANIZATIONS = "organizations";
 
index f45c1dd18cbaa4526e43ea8e4beb9e03da596332..0c8e49da4d7b2a4b40aed12ebbdb9b1d98fdf1a4 100644 (file)
  */
 package org.sonar.scanner.report;
 
+import java.util.AbstractMap;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.annotation.Nonnull;
+import org.sonar.api.batch.scm.ScmProvider;
 import org.sonar.core.config.CorePropertyDefinitions;
 import org.sonar.scanner.config.DefaultConfiguration;
 import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.scanner.protocol.output.ScannerReportWriter;
 import org.sonar.scanner.repository.ContextPropertiesCache;
+import org.sonar.scanner.scm.ScmConfiguration;
 
-public class ContextPropertiesPublisher implements ReportPublisherStep {
+import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDSCM;
 
+public class ContextPropertiesPublisher implements ReportPublisherStep {
   private final ContextPropertiesCache cache;
   private final DefaultConfiguration config;
+  private final ScmConfiguration scmConfiguration;
 
-  public ContextPropertiesPublisher(ContextPropertiesCache cache, DefaultConfiguration config) {
+  public ContextPropertiesPublisher(ContextPropertiesCache cache, DefaultConfiguration config, ScmConfiguration scmConfiguration) {
     this.cache = cache;
     this.config = config;
+    this.scmConfiguration = scmConfiguration;
   }
 
   @Override
@@ -45,7 +51,7 @@ public class ContextPropertiesPublisher implements ReportPublisherStep {
     MapEntryToContextPropertyFunction transformer = new MapEntryToContextPropertyFunction();
 
     // properties defined programmatically by plugins
-    Stream<ScannerReport.ContextProperty> fromCache = cache.getAll().entrySet().stream().map(transformer);
+    Stream<ScannerReport.ContextProperty> fromCache = Stream.concat(cache.getAll().entrySet().stream(), Stream.of(constructScmInfo())).map(transformer);
 
     // properties that are automatically included to report so that
     // they can be included to webhook payloads
@@ -56,6 +62,15 @@ public class ContextPropertiesPublisher implements ReportPublisherStep {
     writer.writeContextProperties(Stream.concat(fromCache, fromSettings).collect(Collectors.toList()));
   }
 
+  private Map.Entry<String, String> constructScmInfo() {
+    ScmProvider scmProvider = scmConfiguration.provider();
+    if (scmProvider != null) {
+      return new AbstractMap.SimpleEntry<>(SONAR_ANALYSIS_DETECTEDSCM, scmProvider.key());
+    } else {
+      return new AbstractMap.SimpleEntry<>(SONAR_ANALYSIS_DETECTEDSCM, "undetected");
+    }
+  }
+
   private static final class MapEntryToContextPropertyFunction implements Function<Map.Entry<String, String>, ScannerReport.ContextProperty> {
     private final ScannerReport.ContextProperty.Builder builder = ScannerReport.ContextProperty.newBuilder();
 
index 91d42673494463ed5726636f3fde109af4892181..db450a00657bdad9c868c6702898eeb22e5d4012 100644 (file)
@@ -32,8 +32,8 @@ import org.sonar.scanner.config.DefaultConfiguration;
 import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.scanner.protocol.output.ScannerReportWriter;
 import org.sonar.scanner.repository.ContextPropertiesCache;
+import org.sonar.scanner.scm.ScmConfiguration;
 
-import static java.util.Collections.emptyList;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -43,11 +43,12 @@ public class ContextPropertiesPublisherTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private ScannerReportWriter writer = mock(ScannerReportWriter.class);
-  private ContextPropertiesCache cache = new ContextPropertiesCache();
-  private DefaultConfiguration config = mock(DefaultConfiguration.class);
-  private Map<String, String> props = new HashMap<>();
-  private ContextPropertiesPublisher underTest = new ContextPropertiesPublisher(cache, config);
+  private final ScannerReportWriter writer = mock(ScannerReportWriter.class);
+  private final ContextPropertiesCache cache = new ContextPropertiesCache();
+  private final DefaultConfiguration config = mock(DefaultConfiguration.class);
+  private final Map<String, String> props = new HashMap<>();
+  private final ScmConfiguration scmConfiguration = mock(ScmConfiguration.class);
+  private final ContextPropertiesPublisher underTest = new ContextPropertiesPublisher(cache, config, scmConfiguration);
 
   @Before
   public void prepareMock() {
@@ -63,17 +64,11 @@ public class ContextPropertiesPublisherTest {
 
     List<ScannerReport.ContextProperty> expected = Arrays.asList(
       newContextProperty("foo1", "bar1"),
-      newContextProperty("foo2", "bar2"));
+      newContextProperty("foo2", "bar2"),
+      newContextProperty("sonar.analysis.detectedscm", "undetected"));
     expectWritten(expected);
   }
 
-  @Test
-  public void publish_writes_no_properties_to_report() {
-    underTest.publish(writer);
-
-    expectWritten(emptyList());
-  }
-
   @Test
   public void publish_settings_prefixed_with_sonar_analysis_for_webhooks() {
     props.put("foo", "should not be exported");
@@ -84,7 +79,8 @@ public class ContextPropertiesPublisherTest {
 
     List<ScannerReport.ContextProperty> expected = Arrays.asList(
       newContextProperty("sonar.analysis.revision", "ab45b3"),
-      newContextProperty("sonar.analysis.build.number", "B123"));
+      newContextProperty("sonar.analysis.build.number", "B123"),
+      newContextProperty("sonar.analysis.detectedscm", "undetected"));
     expectWritten(expected);
   }