From bd5b4a869dfe128ccbebc86af58c4a4bb77962e1 Mon Sep 17 00:00:00 2001 From: Michal Duda Date: Fri, 26 Feb 2021 15:03:43 +0100 Subject: [PATCH] SONAR-14525 include SCM usage information in telemetry --- .../step/PersistAnalysisPropertiesStep.java | 6 +-- .../src/main/java/org/sonar/db/MyBatis.java | 2 + .../db/component/AnalysisPropertiesDao.java | 4 ++ .../component/AnalysisPropertiesMapper.java | 2 + .../ProjectCountPerAnalysisPropertyValue.java | 45 +++++++++++++++++++ .../db/component/AnalysisPropertiesMapper.xml | 12 +++++ .../component/AnalysisPropertiesDaoTest.java | 32 ++++++++++++- .../sonar/server/telemetry/TelemetryData.java | 14 ++++++ .../telemetry/TelemetryDataJsonWriter.java | 16 ++++++- .../TelemetryDataJsonWriterTest.java | 23 +++++++++- .../telemetry/TelemetryDataLoaderImpl.java | 14 ++++-- .../platform/ClusterSystemInfoWriterTest.java | 18 ++++---- .../StandaloneSystemInfoWriterTest.java | 20 ++++----- .../server/telemetry/TelemetryDaemonTest.java | 15 ++++--- .../core/config/CorePropertyDefinitions.java | 1 + .../report/ContextPropertiesPublisher.java | 21 +++++++-- .../ContextPropertiesPublisherTest.java | 26 +++++------ 17 files changed, 214 insertions(+), 57 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/component/ProjectCountPerAnalysisPropertyValue.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStep.java index aa74407325d..ec1055ff4b5 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStep.java @@ -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) diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index f25e7820ece..58239037854 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -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); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesDao.java index 83c5698b07f..c40ad31952f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesDao.java @@ -63,6 +63,10 @@ public class AnalysisPropertiesDao implements Dao { } } + public List selectProjectCountPerAnalysisPropertyValueInLastAnalysis(DbSession session, String analysisPropertyKey) { + return getMapper(session).selectProjectCountPerAnalysisPropertyValueInLastAnalysis(analysisPropertyKey); + } + private static boolean mustBeStoredInClob(String value) { return value.length() > VARCHAR_MAXSIZE; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesMapper.java index fef92d58c06..19080f1413b 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/AnalysisPropertiesMapper.java @@ -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 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 index 00000000000..bdc7055da8c --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ProjectCountPerAnalysisPropertyValue.java @@ -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; + } +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/AnalysisPropertiesMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/AnalysisPropertiesMapper.xml index 865f072c2b1..124b27aad3d 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/AnalysisPropertiesMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/AnalysisPropertiesMapper.xml @@ -21,6 +21,18 @@ analysis_uuid = #{analysisUuid} + + INSERT INTO analysis_properties ( uuid, diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/AnalysisPropertiesDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/AnalysisPropertiesDaoTest.java index 616aa844389..9d49dcecb89 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/AnalysisPropertiesDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/AnalysisPropertiesDaoTest.java @@ -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 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) { diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java index 4710e3f9d75..b625d1aead5 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java @@ -43,6 +43,7 @@ public class TelemetryData { private final Map almIntegrationCountByAlm; private final Map nclocByLanguage; private final List externalAuthenticationProviders; + private final Map 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 getProjectCountByScm() { + return projectCountByScm; + } + static Builder builder() { return new Builder(); } @@ -178,6 +184,8 @@ public class TelemetryData { private Boolean hasUnanalyzedCpp; private List customSecurityConfigs; private List externalAuthenticationProviders; + private Map projectCountByScm; + private Builder() { // enforce static factory method @@ -188,6 +196,11 @@ public class TelemetryData { return this; } + Builder setProjectCountByScm(Map 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); } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java index 17366c51ebe..e675e1dc101 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java @@ -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()); } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java index 141e4b57198..ca3e14f774c 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java @@ -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); } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java index 266a6db942a..c944b32e857 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java @@ -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 projectCountPerScmDetected = dbClient.analysisPropertiesDao() + .selectProjectCountPerAnalysisPropertyValueInLastAnalysis(dbSession, CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDSCM) + .stream() + .collect(Collectors.toMap(ProjectCountPerAnalysisPropertyValue::getPropertyValue, ProjectCountPerAnalysisPropertyValue::getCount)); + data.setProjectCountByScm(projectCountPerScmDetected); } setSecurityCustomConfigIfPresent(data); diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java index f3096ecfe32..560e4461f19 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java @@ -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) { diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java index a7e99d4f402..c22757e9e81 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java @@ -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() { diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java index 423c00cee02..ccf32d67327 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java @@ -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)); } diff --git a/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java b/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java index 55167daa8c1..58162ad28b6 100644 --- a/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java +++ b/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java @@ -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"; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java index f45c1dd18cb..0c8e49da4d7 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ContextPropertiesPublisher.java @@ -19,25 +19,31 @@ */ 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 fromCache = cache.getAll().entrySet().stream().map(transformer); + Stream 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 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, ScannerReport.ContextProperty> { private final ScannerReport.ContextProperty.Builder builder = ScannerReport.ContextProperty.newBuilder(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java index 91d42673494..db450a00657 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ContextPropertiesPublisherTest.java @@ -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 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 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 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 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); } -- 2.39.5