aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-webserver-core
diff options
context:
space:
mode:
authorAlain Kermis <alain.kermis@sonarsource.com>2024-07-08 17:45:13 +0200
committersonartech <sonartech@sonarsource.com>2024-07-24 20:02:47 +0000
commited02521046020a37140eb84ec536c5cdf31e6d4b (patch)
treef772ac842043dc91aa412adc52e7fbd99b07d13f /server/sonar-webserver-core
parent2ab4e9ead92e6f6a64152424303b0d7f0feffe8a (diff)
downloadsonarqube-ed02521046020a37140eb84ec536c5cdf31e6d4b.tar.gz
sonarqube-ed02521046020a37140eb84ec536c5cdf31e6d4b.zip
SONAR-22479 Create new telemetry module
Diffstat (limited to 'server/sonar-webserver-core')
-rw-r--r--server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/QualityProfileDataProviderIT.java169
-rw-r--r--server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/TelemetryDataLoaderImplIT.java742
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/CloudUsageDataProvider.java236
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/QualityProfileDataProvider.java95
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryClient.java127
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDaemon.java169
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java535
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/package-info.java24
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/CloudUsageDataProviderTest.java226
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/FakeServer.java71
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientCompressionTest.java62
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientTest.java84
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java219
-rw-r--r--server/sonar-webserver-core/src/test/resources/org/sonar/server/telemetry/dummy.crt15
-rw-r--r--server/sonar-webserver-core/src/test/resources/org/sonar/server/telemetry/telemetry-example.json56
15 files changed, 0 insertions, 2830 deletions
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/QualityProfileDataProviderIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/QualityProfileDataProviderIT.java
deleted file mode 100644
index e5aefdcd36a..00000000000
--- a/server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/QualityProfileDataProviderIT.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import javax.annotation.Nullable;
-import org.assertj.core.api.Assertions;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ProjectData;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.server.qualityprofile.QProfileComparison;
-
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.sonar.db.qualityprofile.ActiveRuleDto.OVERRIDES;
-
-public class QualityProfileDataProviderIT {
-
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
- private DbClient dbClient = dbTester.getDbClient();
-
- QualityProfileDataProvider underTest = new QualityProfileDataProvider(dbClient, new QProfileComparison(dbClient));
-
- @Test
- public void retrieveQualityProfilesData_whenDefaultRootProfile_shouldReturnRelevantInformation() {
- QProfileDto qProfile1 = createQualityProfile(false, null);
- dbTester.qualityProfiles().setAsDefault(qProfile1);
- Assertions.assertThat(underTest.retrieveQualityProfilesData())
- .extracting(p -> p.uuid(), p -> p.isDefault(), p -> p.isBuiltIn(), p -> p.builtInParent(),
- p -> p.rulesActivatedCount(), p -> p.rulesDeactivatedCount(), p -> p.rulesOverriddenCount())
- .containsExactlyInAnyOrder(tuple(qProfile1.getKee(), true, false, false, null, null, null));
- }
-
- @Test
- public void retrieveQualityProfilesData_whenDefaultChildProfile_shouldReturnRelevantInformation() {
- QProfileDto rootProfile = createQualityProfile(false, null);
-
- QProfileDto childProfile = createQualityProfile(false, rootProfile.getKee());
-
- dbTester.qualityProfiles().setAsDefault(childProfile);
- Assertions.assertThat(underTest.retrieveQualityProfilesData())
- .extracting(p -> p.uuid(), p -> p.isDefault(), p -> p.isBuiltIn(), p -> p.builtInParent(),
- p -> p.rulesActivatedCount(), p -> p.rulesDeactivatedCount(), p -> p.rulesOverriddenCount())
- .containsExactlyInAnyOrder(
- tuple(rootProfile.getKee(), false, false, false, null, null, null),
- tuple(childProfile.getKee(), true, false, false, null, null, null));
- }
-
- @Test
- public void retrieveQualityProfilesData_whenProfileAssignedToProject_shouldReturnProfile() {
- ProjectData projectData = dbTester.components().insertPublicProject();
-
- QProfileDto associatedProfile = createQualityProfile(false, null);
-
- QProfileDto unassociatedProfile = createQualityProfile(false, null);
-
- dbTester.qualityProfiles().associateWithProject(projectData.getProjectDto(), associatedProfile);
-
- Assertions.assertThat(underTest.retrieveQualityProfilesData())
- .extracting(p -> p.uuid(), p -> p.isDefault())
- .containsExactlyInAnyOrder(
- tuple(associatedProfile.getKee(), false),
- tuple(unassociatedProfile.getKee(), false)
- );
- }
-
- @Test
- public void retrieveQualityProfilesData_whenBuiltInParent_shouldReturnBuiltInParent() {
-
- QProfileDto rootBuiltinProfile = createQualityProfile(true, null);
-
- QProfileDto childProfile = createQualityProfile(false, rootBuiltinProfile.getKee());
-
- QProfileDto grandChildProfile = createQualityProfile(false, childProfile.getKee());
-
- dbTester.qualityProfiles().setAsDefault(rootBuiltinProfile, childProfile, grandChildProfile);
-
- Assertions.assertThat(underTest.retrieveQualityProfilesData())
- .extracting(p -> p.uuid(), p -> p.isBuiltIn(), p -> p.builtInParent())
- .containsExactlyInAnyOrder(tuple(rootBuiltinProfile.getKee(), true, null),
- tuple(childProfile.getKee(), false, true),
- tuple(grandChildProfile.getKee(), false, true)
- );
- }
-
- @Test
- public void retrieveQualityProfilesData_whenBuiltInParent_shouldReturnActiveAndUnactiveRules() {
-
- QProfileDto rootBuiltinProfile = createQualityProfile(true, null);
-
- QProfileDto childProfile = createQualityProfile(false, rootBuiltinProfile.getKee());
- RuleDto activatedRule = dbTester.rules().insert();
- RuleDto deactivatedRule = dbTester.rules().insert();
-
- dbTester.qualityProfiles().activateRule(rootBuiltinProfile, deactivatedRule);
- dbTester.qualityProfiles().activateRule(childProfile, activatedRule);
- dbTester.qualityProfiles().setAsDefault(childProfile);
-
- Assertions.assertThat(underTest.retrieveQualityProfilesData())
- .extracting(p -> p.uuid(), p -> p.rulesActivatedCount(), p -> p.rulesDeactivatedCount(), p -> p.rulesOverriddenCount())
- .containsExactlyInAnyOrder(
- tuple(rootBuiltinProfile.getKee(), null, null, null),
- tuple(childProfile.getKee(), 1, 1, 0)
- );
- }
-
- @Test
- public void retrieveQualityProfilesData_whenBuiltInParent_shouldReturnOverriddenRules() {
-
- QProfileDto rootBuiltinProfile = createQualityProfile(true, null);
-
- QProfileDto childProfile = createQualityProfile(false, rootBuiltinProfile.getKee());
- RuleDto rule = dbTester.rules().insert();
- RuleParamDto initialRuleParam = dbTester.rules().insertRuleParam(rule, p -> p.setName("key").setDefaultValue("initial"));
-
-
- ActiveRuleDto activeRuleDto = dbTester.qualityProfiles().activateRule(rootBuiltinProfile, rule);
- dbTester.getDbClient().activeRuleDao().insertParam(dbTester.getSession(), activeRuleDto, newParam(activeRuleDto, initialRuleParam, "key", "value"));
-
- ActiveRuleDto childActivateRule = dbTester.qualityProfiles().activateRule(childProfile, rule, ar -> {
- ar.setInheritance(OVERRIDES);
- });
- dbTester.getDbClient().activeRuleDao().insertParam(dbTester.getSession(), activeRuleDto, newParam(childActivateRule, initialRuleParam, "key", "override"));
-
- dbTester.qualityProfiles().setAsDefault(childProfile);
-
- Assertions.assertThat(underTest.retrieveQualityProfilesData())
- .extracting(p -> p.uuid(), p -> p.rulesActivatedCount(), p -> p.rulesDeactivatedCount(), p -> p.rulesOverriddenCount())
- .containsExactlyInAnyOrder(
- tuple(rootBuiltinProfile.getKee(), null, null, null),
- tuple(childProfile.getKee(), 0, 0, 1));
- }
-
- private static ActiveRuleParamDto newParam(ActiveRuleDto activeRuleDto, RuleParamDto initial, String key, String value) {
- return new ActiveRuleParamDto().setActiveRuleUuid(activeRuleDto.getRuleUuid()).setRulesParameterUuid(initial.getUuid()).setKey(key).setValue(value);
- }
-
- private QProfileDto createQualityProfile(boolean isBuiltIn, @Nullable String parentKee) {
- return dbTester.qualityProfiles().insert(p -> {
- p.setIsBuiltIn(isBuiltIn);
- p.setParentKee(parentKee);
- });
- }
-}
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/TelemetryDataLoaderImplIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/TelemetryDataLoaderImplIT.java
deleted file mode 100644
index c02e03a0881..00000000000
--- a/server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/TelemetryDataLoaderImplIT.java
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.sql.DatabaseMetaData;
-import java.sql.SQLException;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.IntStream;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.core.platform.PlatformEditionProvider;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginRepository;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.alm.setting.AlmSettingDto;
-import org.sonar.db.component.AnalysisPropertyDto;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ProjectData;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.metric.MetricDto;
-import org.sonar.db.newcodeperiod.NewCodePeriodType;
-import org.sonar.db.project.CreationMethod;
-import org.sonar.db.property.PropertyDto;
-import org.sonar.db.qualitygate.QualityGateConditionDto;
-import org.sonar.db.qualitygate.QualityGateDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.user.UserDbTester;
-import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTelemetryDto;
-import org.sonar.server.management.ManagedInstanceService;
-import org.sonar.server.platform.ContainerSupport;
-import org.sonar.server.property.InternalProperties;
-import org.sonar.server.property.MapInternalProperties;
-import org.sonar.server.qualitygate.QualityGateCaycChecker;
-import org.sonar.server.qualitygate.QualityGateFinder;
-import org.sonar.server.qualityprofile.QProfileComparison;
-import org.sonar.server.telemetry.TelemetryData.Branch;
-import org.sonar.server.telemetry.TelemetryData.CloudUsage;
-import org.sonar.server.telemetry.TelemetryData.NewCodeDefinition;
-import org.sonar.server.telemetry.TelemetryData.ProjectStatistics;
-import org.sonar.updatecenter.common.Version;
-
-import static java.util.Arrays.asList;
-import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
-import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
-import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
-import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
-import static org.sonar.api.measures.CoreMetrics.LINES_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
-import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
-import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY;
-import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
-import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDCI;
-import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDSCM;
-import static org.sonar.core.platform.EditionProvider.Edition.COMMUNITY;
-import static org.sonar.core.platform.EditionProvider.Edition.DEVELOPER;
-import static org.sonar.core.platform.EditionProvider.Edition.ENTERPRISE;
-import static org.sonar.db.component.BranchType.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.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
-import static org.sonar.server.telemetry.TelemetryDataLoaderImpl.EXTERNAL_SECURITY_REPORT_EXPORTED_AT;
-
-@RunWith(DataProviderRunner.class)
-public class TelemetryDataLoaderImplIT {
- private final static Long NOW = 100_000_000L;
- public static final String SERVER_ID = "AU-TpxcB-iU5OvuD2FL7";
- private final TestSystem2 system2 = new TestSystem2().setNow(NOW);
-
- @Rule
- public DbTester db = DbTester.create(system2);
-
- private final FakeServer server = new FakeServer();
- private final PluginRepository pluginRepository = mock(PluginRepository.class);
- private final Configuration configuration = mock(Configuration.class);
- private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
- private final ContainerSupport containerSupport = mock(ContainerSupport.class);
- private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
- private final QualityGateFinder qualityGateFinder = new QualityGateFinder(db.getDbClient());
-
- private final QualityProfileDataProvider qualityProfileDataProvider = new QualityProfileDataProvider(db.getDbClient(), new QProfileComparison(db.getDbClient()));
- private final InternalProperties internalProperties = spy(new MapInternalProperties());
- private final ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class);
- private final CloudUsageDataProvider cloudUsageDataProvider = mock(CloudUsageDataProvider.class);
-
- private final TelemetryDataLoader communityUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
- internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider, qualityProfileDataProvider);
- private final TelemetryDataLoader commercialUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
- internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider, qualityProfileDataProvider);
-
- private QualityGateDto builtInDefaultQualityGate;
- private MetricDto bugsDto;
- private MetricDto vulnerabilitiesDto;
- private MetricDto securityHotspotsDto;
- private MetricDto technicalDebtDto;
- private MetricDto developmentCostDto;
-
- @Before
- public void setUpBuiltInQualityGate() {
- String builtInQgName = "Sonar way";
- builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true));
- when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(NON_COMPLIANT);
- db.qualityGates().setDefaultQualityGate(builtInDefaultQualityGate);
-
- bugsDto = db.measures().insertMetric(m -> m.setKey(BUGS_KEY));
- vulnerabilitiesDto = db.measures().insertMetric(m -> m.setKey(VULNERABILITIES_KEY));
- securityHotspotsDto = db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_KEY));
- technicalDebtDto = db.measures().insertMetric(m -> m.setKey(TECHNICAL_DEBT_KEY));
- developmentCostDto = db.measures().insertMetric(m -> m.setKey(DEVELOPMENT_COST_KEY));
- }
-
- @Test
- public void send_telemetry_data() {
- String version = "7.5.4";
- Long analysisDate = 1L;
- Long lastConnectionDate = 5L;
-
- server.setId(SERVER_ID);
- server.setVersion(version);
- List<PluginInfo> plugins = asList(newPlugin("java", "4.12.0.11033"), newPlugin("scmgit", "1.2"), new PluginInfo("other"));
- when(pluginRepository.getPluginInfos()).thenReturn(plugins);
- when(editionProvider.get()).thenReturn(Optional.of(DEVELOPER));
-
- List<UserDto> activeUsers = composeActiveUsers(3);
-
- // update last connection
- activeUsers.forEach(u -> db.users().updateLastConnectionDate(u, 5L));
-
- UserDto inactiveUser = db.users().insertUser(u -> u.setActive(false).setExternalIdentityProvider("provider0"));
-
- MetricDto lines = db.measures().insertMetric(m -> m.setKey(LINES_KEY));
- MetricDto ncloc = db.measures().insertMetric(m -> m.setKey(NCLOC_KEY));
- MetricDto coverage = db.measures().insertMetric(m -> m.setKey(COVERAGE_KEY));
- MetricDto nclocDistrib = db.measures().insertMetric(m -> m.setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY));
-
- ProjectData projectData1 = db.components().insertPrivateProject();
- ComponentDto mainBranch1 = projectData1.getMainBranchComponent();
- var branch1 = db.components().insertProjectBranch(mainBranch1, branchDto -> branchDto.setKey("reference"));
- var branch2 = db.components().insertProjectBranch(mainBranch1, branchDto -> branchDto.setKey("custom"));
- db.measures().insertLiveMeasure(mainBranch1, lines, m -> m.setValue(110d));
- db.measures().insertLiveMeasure(mainBranch1, ncloc, m -> m.setValue(110d));
- db.measures().insertLiveMeasure(mainBranch1, coverage, m -> m.setValue(80d));
- db.measures().insertLiveMeasure(mainBranch1, nclocDistrib, m -> m.setValue(null).setData("java=70;js=30;kotlin=10"));
- db.measures().insertLiveMeasure(mainBranch1, bugsDto, m -> m.setValue(1d));
- db.measures().insertLiveMeasure(mainBranch1, vulnerabilitiesDto, m -> m.setValue(1d).setData((String) null));
- db.measures().insertLiveMeasure(mainBranch1, securityHotspotsDto, m -> m.setValue(1d).setData((String) null));
- db.measures().insertLiveMeasure(mainBranch1, developmentCostDto, m -> m.setData("50").setValue(null));
- db.measures().insertLiveMeasure(mainBranch1, technicalDebtDto, m -> m.setValue(5d).setData((String) null));
- // Measures on other branches
- db.measures().insertLiveMeasure(branch1, technicalDebtDto, m -> m.setValue(6d).setData((String) null));
- db.measures().insertLiveMeasure(branch2, technicalDebtDto, m -> m.setValue(7d).setData((String) null));
-
- ProjectData projectData2 = db.components().insertPrivateProject();
- ComponentDto mainBranch2 = projectData2.getMainBranchComponent();
- db.measures().insertLiveMeasure(mainBranch2, lines, m -> m.setValue(200d));
- db.measures().insertLiveMeasure(mainBranch2, ncloc, m -> m.setValue(200d));
- db.measures().insertLiveMeasure(mainBranch2, coverage, m -> m.setValue(80d));
- db.measures().insertLiveMeasure(mainBranch2, nclocDistrib, m -> m.setValue(null).setData("java=180;js=20"));
-
- SnapshotDto project1Analysis = db.components().insertSnapshot(mainBranch1, t -> t.setLast(true).setAnalysisDate(analysisDate));
- SnapshotDto project2Analysis = db.components().insertSnapshot(mainBranch2, t -> t.setLast(true).setAnalysisDate(analysisDate));
- db.measures().insertMeasure(mainBranch1, project1Analysis, nclocDistrib, m -> m.setData("java=70;js=30;kotlin=10"));
- db.measures().insertMeasure(mainBranch2, project2Analysis, nclocDistrib, m -> m.setData("java=180;js=20"));
-
- insertAnalysisProperty(project1Analysis, "prop-uuid-1", SONAR_ANALYSIS_DETECTEDCI, "ci-1");
- insertAnalysisProperty(project2Analysis, "prop-uuid-2", SONAR_ANALYSIS_DETECTEDCI, "ci-2");
- insertAnalysisProperty(project1Analysis, "prop-uuid-3", SONAR_ANALYSIS_DETECTEDSCM, "scm-1");
- insertAnalysisProperty(project2Analysis, "prop-uuid-4", SONAR_ANALYSIS_DETECTEDSCM, "scm-2");
-
- // alm
- db.almSettings().insertAzureAlmSetting();
- db.almSettings().insertGitHubAlmSetting();
- AlmSettingDto almSettingDto = db.almSettings().insertAzureAlmSetting(a -> a.setUrl("https://dev.azure.com"));
- AlmSettingDto gitHubAlmSetting = db.almSettings().insertGitHubAlmSetting(a -> a.setUrl("https://api.github.com"));
- db.almSettings().insertAzureProjectAlmSetting(almSettingDto, projectData1.getProjectDto());
- db.almSettings().insertGitlabProjectAlmSetting(gitHubAlmSetting, projectData2.getProjectDto(), true);
-
- // quality gates
- QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG1").setBuiltIn(true));
- QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG2"));
-
- QualityGateConditionDto condition1 = db.qualityGates().addCondition(qualityGate1, vulnerabilitiesDto, c -> c.setOperator("GT").setErrorThreshold("80"));
- QualityGateConditionDto condition2 = db.qualityGates().addCondition(qualityGate2, securityHotspotsDto, c -> c.setOperator("LT").setErrorThreshold("2"));
-
- // quality profiles
- QProfileDto javaQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("java"));
- QProfileDto kotlinQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("kotlin"));
- QProfileDto jsQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("js"));
- db.qualityProfiles().associateWithProject(projectData1.getProjectDto(), javaQP, kotlinQP, jsQP);
- db.qualityProfiles().associateWithProject(projectData2.getProjectDto(), javaQP, jsQP);
-
- QProfileDto qualityProfile1 = db.qualityProfiles().insert(qp -> qp.setIsBuiltIn(true));
- QProfileDto qualityProfile2 = db.qualityProfiles().insert();
- db.qualityProfiles().setAsDefault(qualityProfile1, qualityProfile2);
-
- // link one project to a non-default QG
- db.qualityGates().associateProjectToQualityGate(db.components().getProjectDtoByMainBranch(mainBranch1), qualityGate1);
-
- db.newCodePeriods().insert(projectData1.projectUuid(), NewCodePeriodType.NUMBER_OF_DAYS, "30");
- db.newCodePeriods().insert(projectData1.projectUuid(), 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(SERVER_ID);
- assertThat(data.getVersion()).isEqualTo(version);
- assertThat(data.getEdition()).contains(DEVELOPER);
- assertThat(data.getDefaultQualityGate()).isEqualTo(builtInDefaultQualityGate.getUuid());
- assertThat(data.getSonarWayQualityGate()).isEqualTo(builtInDefaultQualityGate.getUuid());
- assertThat(data.getNcdId()).isEqualTo(NewCodeDefinition.getInstanceDefault().hashCode());
- assertThat(data.getMessageSequenceNumber()).isOne();
- assertDatabaseMetadata(data.getDatabase());
- assertThat(data.getPlugins()).containsOnly(
- entry("java", "4.12.0.11033"), entry("scmgit", "1.2"), entry("other", "undefined"));
- assertThat(data.isInContainer()).isFalse();
-
- assertThat(data.getUserTelemetries())
- .extracting(UserTelemetryDto::getUuid, UserTelemetryDto::getLastConnectionDate, UserTelemetryDto::getLastSonarlintConnectionDate, UserTelemetryDto::isActive)
- .containsExactlyInAnyOrder(
- tuple(activeUsers.get(0).getUuid(), lastConnectionDate, activeUsers.get(0).getLastSonarlintConnectionDate(), true),
- tuple(activeUsers.get(1).getUuid(), lastConnectionDate, activeUsers.get(1).getLastSonarlintConnectionDate(), true),
- tuple(activeUsers.get(2).getUuid(), lastConnectionDate, activeUsers.get(2).getLastSonarlintConnectionDate(), true),
- tuple(inactiveUser.getUuid(), null, inactiveUser.getLastSonarlintConnectionDate(), false));
- assertThat(data.getProjects())
- .extracting(TelemetryData.Project::projectUuid, TelemetryData.Project::language, TelemetryData.Project::loc, TelemetryData.Project::lastAnalysis)
- .containsExactlyInAnyOrder(
- tuple(projectData1.projectUuid(), "java", 70L, analysisDate),
- tuple(projectData1.projectUuid(), "js", 30L, analysisDate),
- tuple(projectData1.projectUuid(), "kotlin", 10L, analysisDate),
- tuple(projectData2.projectUuid(), "java", 180L, analysisDate),
- tuple(projectData2.projectUuid(), "js", 20L, analysisDate));
- assertThat(data.getProjectStatistics())
- .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,
- ProjectStatistics::isMonorepo)
- .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, false),
- tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud", Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
- Optional.empty(), instanceNcdId, true));
-
- assertThat(data.getBranches())
- .extracting(Branch::branchUuid, Branch::ncdId)
- .containsExactlyInAnyOrder(
- tuple(branch1.uuid(), projectNcdId),
- tuple(branch2.uuid(), branchNcdId),
- tuple(mainBranch1.uuid(), projectNcdId),
- tuple(mainBranch2.uuid(), instanceNcdId));
-
- assertThat(data.getNewCodeDefinitions())
- .extracting(NewCodeDefinition::scope, NewCodeDefinition::type, NewCodeDefinition::value)
- .containsExactlyInAnyOrder(
- 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,
- qg -> qg.conditions().stream()
- .map(condition -> tuple(condition.getMetricKey(), condition.getOperator().getDbValue(), condition.getErrorThreshold(), condition.isOnLeakPeriod()))
- .toList())
- .containsExactlyInAnyOrder(
- tuple(builtInDefaultQualityGate.getUuid(), "non-compliant", Collections.emptyList()),
- tuple(qualityGate1.getUuid(), "non-compliant", List.of(tuple(vulnerabilitiesDto.getKey(), condition1.getOperator(), condition1.getErrorThreshold(), false))),
- tuple(qualityGate2.getUuid(), "non-compliant", List.of(tuple(securityHotspotsDto.getKey(), condition2.getOperator(), condition2.getErrorThreshold(), false))));
-
- assertThat(data.getQualityProfiles())
- .extracting(TelemetryData.QualityProfile::uuid, TelemetryData.QualityProfile::isBuiltIn)
- .containsExactlyInAnyOrder(
- tuple(qualityProfile1.getKee(), qualityProfile1.isBuiltIn()),
- tuple(qualityProfile2.getKee(), qualityProfile2.isBuiltIn()),
- tuple(jsQP.getKee(), jsQP.isBuiltIn()),
- tuple(javaQP.getKee(), javaQP.isBuiltIn()),
- tuple(kotlinQP.getKee(), kotlinQP.isBuiltIn()));
-
- }
-
- @Test
- public void send_branch_measures_data() {
- Long analysisDate = ZonedDateTime.now(ZoneId.systemDefault()).toInstant().toEpochMilli();
-
- MetricDto qg = db.measures().insertMetric(m -> m.setKey(ALERT_STATUS_KEY));
-
- ProjectData projectData1 = db.components().insertPrivateProject();
- ComponentDto mainBranch1 = projectData1.getMainBranchComponent();
-
- ProjectData projectData2 = db.components().insertPrivateProject();
- ComponentDto mainBranch2 = projectData2.getMainBranchComponent();
-
- SnapshotDto project1Analysis1 = db.components().insertSnapshot(mainBranch1, t -> t.setLast(true).setAnalysisDate(analysisDate));
- SnapshotDto project1Analysis2 = db.components().insertSnapshot(mainBranch1, t -> t.setLast(true).setAnalysisDate(analysisDate));
- SnapshotDto project2Analysis = db.components().insertSnapshot(mainBranch2, t -> t.setLast(true).setAnalysisDate(analysisDate));
- db.measures().insertMeasure(mainBranch1, project1Analysis1, qg, pm -> pm.setData("OK"));
- db.measures().insertMeasure(mainBranch1, project1Analysis2, qg, pm -> pm.setData("ERROR"));
- db.measures().insertMeasure(mainBranch2, project2Analysis, qg, pm -> pm.setData("ERROR"));
-
- var branch1 = db.components().insertProjectBranch(mainBranch1, branchDto -> branchDto.setKey("reference"));
- var branch2 = db.components().insertProjectBranch(mainBranch1, branchDto -> branchDto.setKey("custom"));
-
- db.newCodePeriods().insert(projectData1.projectUuid(), NewCodePeriodType.NUMBER_OF_DAYS, "30");
- db.newCodePeriods().insert(projectData1.projectUuid(), 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.getBranches())
- .extracting(Branch::branchUuid, Branch::ncdId, Branch::greenQualityGateCount, Branch::analysisCount)
- .containsExactlyInAnyOrder(
- tuple(branch1.uuid(), projectNcdId, 0, 0),
- tuple(branch2.uuid(), branchNcdId, 0, 0),
- tuple(mainBranch1.uuid(), projectNcdId, 1, 2),
- tuple(mainBranch2.uuid(), instanceNcdId, 0, 1));
-
- }
-
- private List<UserDto> composeActiveUsers(int count) {
- UserDbTester userDbTester = db.users();
- Function<Integer, Consumer<UserDto>> userConfigurator = index -> user -> user.setExternalIdentityProvider("provider" + index).setLastSonarlintConnectionDate(index * 2L);
-
- return IntStream
- .rangeClosed(1, count)
- .mapToObj(userConfigurator::apply)
- .map(userDbTester::insertUser)
- .toList();
- }
-
- private void assertDatabaseMetadata(TelemetryData.Database database) {
- try (DbSession dbSession = db.getDbClient().openSession(false)) {
- DatabaseMetaData metadata = dbSession.getConnection().getMetaData();
- assertThat(database.name()).isEqualTo("H2");
- assertThat(database.version()).isEqualTo(metadata.getDatabaseProductVersion());
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Test
- public void take_largest_branch_snapshot_project_data() {
- server.setId(SERVER_ID).setVersion("7.5.4");
-
- MetricDto lines = db.measures().insertMetric(m -> m.setKey(LINES_KEY));
- MetricDto ncloc = db.measures().insertMetric(m -> m.setKey(NCLOC_KEY));
- MetricDto coverage = db.measures().insertMetric(m -> m.setKey(COVERAGE_KEY));
- MetricDto nclocDistrib = db.measures().insertMetric(m -> m.setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY));
-
- ProjectData projectData = db.components().insertPublicProject();
-
- QProfileDto javaQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("java"));
- QProfileDto kotlinQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("kotlin"));
- QProfileDto jsQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("js"));
- db.qualityProfiles().associateWithProject(projectData.getProjectDto(), javaQP, kotlinQP, jsQP);
-
- ComponentDto mainBranch = projectData.getMainBranchComponent();
- db.measures().insertLiveMeasure(mainBranch, lines, m -> m.setValue(110d));
- db.measures().insertLiveMeasure(mainBranch, ncloc, m -> m.setValue(110d));
- db.measures().insertLiveMeasure(mainBranch, coverage, m -> m.setValue(80d));
- db.measures().insertLiveMeasure(mainBranch, nclocDistrib, m -> m.setValue(null).setData("java=70;js=30;kotlin=10"));
-
- ComponentDto branch = db.components().insertProjectBranch(mainBranch, b -> b.setBranchType(BRANCH));
- db.measures().insertLiveMeasure(branch, lines, m -> m.setValue(180d));
- db.measures().insertLiveMeasure(branch, ncloc, m -> m.setValue(180d));
- db.measures().insertLiveMeasure(branch, coverage, m -> m.setValue(80d));
- db.measures().insertLiveMeasure(branch, nclocDistrib, m -> m.setValue(null).setData("java=100;js=50;kotlin=30"));
-
- SnapshotDto project1Analysis = db.components().insertSnapshot(mainBranch, t -> t.setLast(true));
- SnapshotDto project2Analysis = db.components().insertSnapshot(branch, t -> t.setLast(true));
- db.measures().insertMeasure(mainBranch, project1Analysis, nclocDistrib, m -> m.setData("java=70;js=30;kotlin=10"));
- db.measures().insertMeasure(branch, project2Analysis, nclocDistrib, m -> m.setData("java=100;js=50;kotlin=30"));
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.getProjects()).extracting(TelemetryData.Project::projectUuid, TelemetryData.Project::language, TelemetryData.Project::loc)
- .containsExactlyInAnyOrder(
- tuple(projectData.projectUuid(), "java", 100L),
- tuple(projectData.projectUuid(), "js", 50L),
- tuple(projectData.projectUuid(), "kotlin", 30L));
- assertThat(data.getProjectStatistics())
- .extracting(ProjectStatistics::getBranchCount, ProjectStatistics::getPullRequestCount,
- ProjectStatistics::getScm, ProjectStatistics::getCi)
- .containsExactlyInAnyOrder(
- tuple(2L, 0L, "undetected", "undetected"));
- }
-
- @Test
- public void load_shouldProvideQualityProfileInProjectSection() {
- server.setId(SERVER_ID).setVersion("7.5.4");
- MetricDto ncloc = db.measures().insertMetric(m -> m.setKey(NCLOC_KEY));
- MetricDto nclocDistrib = db.measures().insertMetric(m -> m.setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY));
-
- ProjectData projectData = db.components().insertPublicProject();
-
- // default quality profile
- QProfileDto javaQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("java"));
- QProfileDto kotlinQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("kotlin"));
- db.qualityProfiles().setAsDefault(javaQP, kotlinQP);
- // selected quality profile
- QProfileDto jsQP = db.qualityProfiles().insert(qProfileDto -> qProfileDto.setLanguage("js"));
- db.qualityProfiles().associateWithProject(projectData.getProjectDto(), jsQP);
-
- ComponentDto mainBranch = projectData.getMainBranchComponent();
- db.measures().insertLiveMeasure(mainBranch, ncloc, m -> m.setValue(110d));
- db.measures().insertLiveMeasure(mainBranch, nclocDistrib, m -> m.setValue(null).setData("java=70;js=30;kotlin=10"));
-
- ComponentDto branch = db.components().insertProjectBranch(mainBranch, b -> b.setBranchType(BRANCH));
- db.measures().insertLiveMeasure(branch, ncloc, m -> m.setValue(180d));
- db.measures().insertLiveMeasure(branch, nclocDistrib, m -> m.setValue(null).setData("java=100;js=50;kotlin=30"));
-
- SnapshotDto project1Analysis = db.components().insertSnapshot(mainBranch, t -> t.setLast(true));
- SnapshotDto project2Analysis = db.components().insertSnapshot(branch, t -> t.setLast(true));
- db.measures().insertMeasure(mainBranch, project1Analysis, nclocDistrib, m -> m.setData("java=70;js=30;kotlin=10"));
- db.measures().insertMeasure(branch, project2Analysis, nclocDistrib, m -> m.setData("java=100;js=50;kotlin=30"));
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.getProjects()).extracting(TelemetryData.Project::projectUuid, TelemetryData.Project::language, TelemetryData.Project::qualityProfile)
- .containsExactlyInAnyOrder(
- tuple(projectData.projectUuid(), "java", javaQP.getKee()),
- tuple(projectData.projectUuid(), "js", jsQP.getKee()),
- tuple(projectData.projectUuid(), "kotlin", kotlinQP.getKee()));
- }
-
- @Test
- public void load_shouldProvideCreationMethodInProjectStatisticsSection() {
- server.setId(SERVER_ID).setVersion("7.5.4");
-
- ProjectData projectData1 = db.components().insertPrivateProjectWithCreationMethod(CreationMethod.LOCAL_API);
- ProjectData projectData2 = db.components().insertPrivateProjectWithCreationMethod(CreationMethod.LOCAL_BROWSER);
- ProjectData projectData3 = db.components().insertPrivateProjectWithCreationMethod(CreationMethod.UNKNOWN);
- ProjectData projectData4 = db.components().insertPrivateProjectWithCreationMethod(CreationMethod.SCANNER_API);
- ProjectData projectData5 = db.components().insertPrivateProjectWithCreationMethod(CreationMethod.ALM_IMPORT_BROWSER);
- ProjectData projectData6 = db.components().insertPrivateProjectWithCreationMethod(CreationMethod.ALM_IMPORT_API);
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.getProjectStatistics()).extracting(TelemetryData.ProjectStatistics::getProjectUuid, TelemetryData.ProjectStatistics::getCreationMethod)
- .containsExactlyInAnyOrder(
- tuple(projectData1.projectUuid(), CreationMethod.LOCAL_API),
- tuple(projectData2.projectUuid(), CreationMethod.LOCAL_BROWSER),
- tuple(projectData3.projectUuid(), CreationMethod.UNKNOWN),
- tuple(projectData4.projectUuid(), CreationMethod.SCANNER_API),
- tuple(projectData5.projectUuid(), CreationMethod.ALM_IMPORT_BROWSER),
- tuple(projectData6.projectUuid(), CreationMethod.ALM_IMPORT_API));
- }
-
- @Test
- public void test_ncd_on_community_edition() {
- server.setId(SERVER_ID).setVersion("7.5.4");
- when(editionProvider.get()).thenReturn(Optional.of(COMMUNITY));
-
- ProjectData project = db.components().insertPublicProject();
-
- ComponentDto branch = db.components().insertProjectBranch(project.getMainBranchComponent(), b -> b.setBranchType(BRANCH));
-
- db.newCodePeriods().insert(project.projectUuid(), branch.branchUuid(), NewCodePeriodType.NUMBER_OF_DAYS, "30");
-
- var projectNcdId = new NewCodeDefinition(NewCodePeriodType.NUMBER_OF_DAYS.name(), "30", "project").hashCode();
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.getProjectStatistics())
- .extracting(ProjectStatistics::getBranchCount, ProjectStatistics::getNcdId)
- .containsExactlyInAnyOrder(tuple(2L, projectNcdId));
-
- assertThat(data.getBranches())
- .extracting(Branch::branchUuid, Branch::ncdId)
- .contains(tuple(branch.uuid(), projectNcdId));
- }
-
- @Test
- public void data_contains_weekly_count_sonarlint_users() {
- db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 100_000L));
- db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW));
- // these don't count
- db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 1_000_000_000L));
- db.users().insertUser();
-
- TelemetryData data = communityUnderTest.load();
- assertThat(data.getUserTelemetries())
- .hasSize(4);
- }
-
- @Test
- public void send_server_id_and_version() {
- String id = randomAlphanumeric(40);
- String version = randomAlphanumeric(10);
- server.setId(id);
- server.setVersion(version);
-
- TelemetryData data = communityUnderTest.load();
- assertThat(data.getServerId()).isEqualTo(id);
- assertThat(data.getVersion()).isEqualTo(version);
-
- data = commercialUnderTest.load();
- assertThat(data.getServerId()).isEqualTo(id);
- assertThat(data.getVersion()).isEqualTo(version);
- }
-
- @Test
- public void send_server_installation_date_and_installation_version() {
- String installationVersion = "7.9.BEST.LTS.EVER";
- Long installationDate = 1546300800000L; // 2019/01/01
- internalProperties.write(InternalProperties.INSTALLATION_DATE, String.valueOf(installationDate));
- internalProperties.write(InternalProperties.INSTALLATION_VERSION, installationVersion);
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.getInstallationDate()).isEqualTo(installationDate);
- assertThat(data.getInstallationVersion()).isEqualTo(installationVersion);
- }
-
- @Test
- public void send_correct_sequence_number() {
- internalProperties.write(TelemetryDaemon.I_PROP_MESSAGE_SEQUENCE, "10");
- TelemetryData data = communityUnderTest.load();
- assertThat(data.getMessageSequenceNumber()).isEqualTo(11L);
- }
-
- @Test
- public void do_not_send_server_installation_details_if_missing_property() {
- TelemetryData data = communityUnderTest.load();
- assertThat(data.getInstallationDate()).isNull();
- assertThat(data.getInstallationVersion()).isNull();
-
- data = commercialUnderTest.load();
- assertThat(data.getInstallationDate()).isNull();
- assertThat(data.getInstallationVersion()).isNull();
- }
-
- @Test
- public void send_unanalyzed_languages_flags_when_edition_is_community() {
- when(editionProvider.get()).thenReturn(Optional.of(COMMUNITY));
- MetricDto unanalyzedC = db.measures().insertMetric(m -> m.setKey(UNANALYZED_C_KEY));
- MetricDto unanalyzedCpp = db.measures().insertMetric(m -> m.setKey(UNANALYZED_CPP_KEY));
- ComponentDto project1 = db.components().insertPublicProject().getMainBranchComponent();
- ComponentDto project2 = db.components().insertPublicProject().getMainBranchComponent();
- db.measures().insertLiveMeasure(project1, unanalyzedC);
- db.measures().insertLiveMeasure(project2, unanalyzedC);
- db.measures().insertLiveMeasure(project2, unanalyzedCpp);
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.hasUnanalyzedC().get()).isTrue();
- assertThat(data.hasUnanalyzedCpp().get()).isTrue();
- }
-
- @Test
- public void do_not_send_unanalyzed_languages_flags_when_edition_is_not_community() {
- when(editionProvider.get()).thenReturn(Optional.of(DEVELOPER));
- MetricDto unanalyzedC = db.measures().insertMetric(m -> m.setKey(UNANALYZED_C_KEY));
- MetricDto unanalyzedCpp = db.measures().insertMetric(m -> m.setKey(UNANALYZED_CPP_KEY));
- ComponentDto project1 = db.components().insertPublicProject().getMainBranchComponent();
- ComponentDto project2 = db.components().insertPublicProject().getMainBranchComponent();
- db.measures().insertLiveMeasure(project1, unanalyzedC);
- db.measures().insertLiveMeasure(project2, unanalyzedCpp);
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.hasUnanalyzedC()).isEmpty();
- assertThat(data.hasUnanalyzedCpp()).isEmpty();
- }
-
- @Test
- public void unanalyzed_languages_flags_are_set_to_false_when_no_unanalyzed_languages_and_edition_is_community() {
- when(editionProvider.get()).thenReturn(Optional.of(COMMUNITY));
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.hasUnanalyzedC().get()).isFalse();
- assertThat(data.hasUnanalyzedCpp().get()).isFalse();
- }
-
- @Test
- public void populate_security_custom_config_for_languages_on_enterprise() {
- when(editionProvider.get()).thenReturn(Optional.of(ENTERPRISE));
-
- when(configuration.get("sonar.security.config.javasecurity")).thenReturn(Optional.of("{}"));
- when(configuration.get("sonar.security.config.phpsecurity")).thenReturn(Optional.of("{}"));
- when(configuration.get("sonar.security.config.pythonsecurity")).thenReturn(Optional.of("{}"));
- when(configuration.get("sonar.security.config.roslyn.sonaranalyzer.security.cs")).thenReturn(Optional.of("{}"));
-
- TelemetryData data = commercialUnderTest.load();
-
- assertThat(data.getCustomSecurityConfigs())
- .containsExactlyInAnyOrder("java", "php", "python", "csharp");
- }
-
- @Test
- public void skip_security_custom_config_on_community() {
- when(editionProvider.get()).thenReturn(Optional.of(COMMUNITY));
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.getCustomSecurityConfigs()).isEmpty();
- }
-
- @Test
- public void undetected_alm_ci_slm_data() {
- server.setId(SERVER_ID).setVersion("7.5.4");
- db.components().insertPublicProject().getMainBranchComponent();
- TelemetryData data = communityUnderTest.load();
- assertThat(data.getProjectStatistics())
- .extracting(ProjectStatistics::getDevopsPlatform, ProjectStatistics::getScm, ProjectStatistics::getCi)
- .containsExactlyInAnyOrder(tuple("undetected", "undetected", "undetected"));
- }
-
- @Test
- public void givenExistingExternalSecurityReport_whenTelemetryIsGenerated_payloadShouldContainLastUsageDate() {
- server.setId(SERVER_ID).setVersion("7.5.4");
- ProjectData projectData = db.components().insertPublicProject();
- db.getDbClient().propertiesDao().saveProperty(new PropertyDto().setKey(EXTERNAL_SECURITY_REPORT_EXPORTED_AT).setEntityUuid(projectData.projectUuid()).setValue("1"));
-
- TelemetryData data = communityUnderTest.load();
-
- assertThat(data.getProjectStatistics()).isNotEmpty();
- assertThat(data.getProjectStatistics().get(0).getExternalSecurityReportExportedAt()).isPresent()
- .get().isEqualTo(1L);
- }
-
- @Test
- @UseDataProvider("getManagedInstanceData")
- public void managedInstanceData_containsCorrectInformation(boolean isManaged, String provider) {
- when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(isManaged);
- when(managedInstanceService.getProviderName()).thenReturn(provider);
-
- TelemetryData data = commercialUnderTest.load();
-
- TelemetryData.ManagedInstanceInformation managedInstance = data.getManagedInstanceInformation();
- assertThat(managedInstance.isManaged()).isEqualTo(isManaged);
- assertThat(managedInstance.provider()).isEqualTo(provider);
- }
-
- @Test
- public void load_shouldContainCloudUsage() {
- CloudUsage cloudUsage = new CloudUsage(true, "1.27", "linux/amd64", "5.4.181-99.354.amzn2.x86_64", "10.1.0", "docker", false);
- when(cloudUsageDataProvider.getCloudUsage()).thenReturn(cloudUsage);
-
- TelemetryData data = commercialUnderTest.load();
- assertThat(data.getCloudUsage()).isEqualTo(cloudUsage);
- }
-
- @Test
- public void default_quality_gate_sent_with_project() {
- db.components().insertPublicProject().getMainBranchComponent();
- QualityGateDto qualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("anything").setBuiltIn(true));
- db.qualityGates().setDefaultQualityGate(qualityGate);
- TelemetryData data = communityUnderTest.load();
- assertThat(data.getProjectStatistics())
- .extracting(ProjectStatistics::getQualityGate)
- .containsOnly(qualityGate.getUuid());
- }
-
- private PluginInfo newPlugin(String key, String version) {
- return new PluginInfo(key)
- .setVersion(Version.create(version));
- }
-
- private void insertAnalysisProperty(SnapshotDto snapshotDto, String uuid, String key, String value) {
- db.getDbClient().analysisPropertiesDao().insert(db.getSession(), new AnalysisPropertyDto()
- .setUuid(uuid)
- .setAnalysisUuid(snapshotDto.getUuid())
- .setKey(key)
- .setValue(value)
- .setCreatedAt(1L));
- }
-
- @DataProvider
- public static Set<String> getScimFeatureStatues() {
- HashSet<String> result = new HashSet<>();
- result.add("true");
- result.add("false");
- result.add(null);
- return result;
- }
-
- @DataProvider
- public static Object[][] getManagedInstanceData() {
- return new Object[][] {
- {true, "scim"},
- {true, "github"},
- {true, "gitlab"},
- {false, null},
- };
- }
-}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/CloudUsageDataProvider.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/CloudUsageDataProvider.java
deleted file mode 100644
index 7959407d245..00000000000
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/CloudUsageDataProvider.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.gson.Gson;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.SecureRandom;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.util.Collection;
-import java.util.Scanner;
-import java.util.function.Supplier;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-import okhttp3.internal.tls.OkHostnameVerifier;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.System2;
-import org.sonar.server.platform.ContainerSupport;
-import org.sonar.server.util.Paths2;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Objects.requireNonNull;
-
-@ServerSide
-public class CloudUsageDataProvider {
-
- private static final Logger LOG = LoggerFactory.getLogger(CloudUsageDataProvider.class);
-
- private static final String SERVICEACCOUNT_CA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
- static final String KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST";
- static final String KUBERNETES_SERVICE_PORT = "KUBERNETES_SERVICE_PORT";
- static final String SONAR_HELM_CHART_VERSION = "SONAR_HELM_CHART_VERSION";
- static final String DOCKER_RUNNING = "DOCKER_RUNNING";
- private static final String[] KUBERNETES_PROVIDER_COMMAND = {"bash", "-c", "uname -r"};
- private static final int KUBERNETES_PROVIDER_MAX_SIZE = 100;
- private final ContainerSupport containerSupport;
- private final System2 system2;
- private final Paths2 paths2;
- private final Supplier<ProcessBuilder> processBuilderSupplier;
- private OkHttpClient httpClient;
- private TelemetryData.CloudUsage cloudUsageData;
-
- @Inject
- public CloudUsageDataProvider(ContainerSupport containerSupport, System2 system2, Paths2 paths2) {
- this(containerSupport, system2, paths2, ProcessBuilder::new, null);
- if (isOnKubernetes()) {
- initHttpClient();
- }
- }
-
- @VisibleForTesting
- CloudUsageDataProvider(ContainerSupport containerSupport, System2 system2, Paths2 paths2, Supplier<ProcessBuilder> processBuilderSupplier,
- @Nullable OkHttpClient httpClient) {
- this.containerSupport = containerSupport;
- this.system2 = system2;
- this.paths2 = paths2;
- this.processBuilderSupplier = processBuilderSupplier;
- this.httpClient = httpClient;
- }
-
- public TelemetryData.CloudUsage getCloudUsage() {
- if (cloudUsageData != null) {
- return cloudUsageData;
- }
-
- String kubernetesVersion = null;
- String kubernetesPlatform = null;
-
- if (isOnKubernetes()) {
- VersionInfo versionInfo = getVersionInfo();
- if (versionInfo != null) {
- kubernetesVersion = versionInfo.major() + "." + versionInfo.minor();
- kubernetesPlatform = versionInfo.platform();
- }
- }
-
- cloudUsageData = new TelemetryData.CloudUsage(
- isOnKubernetes(),
- kubernetesVersion,
- kubernetesPlatform,
- getKubernetesProvider(),
- getOfficialHelmChartVersion(),
- containerSupport.getContainerContext(),
- isOfficialImageUsed());
-
- return cloudUsageData;
- }
-
- private boolean isOnKubernetes() {
- return StringUtils.isNotBlank(system2.envVariable(KUBERNETES_SERVICE_HOST));
- }
-
- @CheckForNull
- private String getOfficialHelmChartVersion() {
- return system2.envVariable(SONAR_HELM_CHART_VERSION);
- }
-
- private boolean isOfficialImageUsed() {
- return Boolean.parseBoolean(system2.envVariable(DOCKER_RUNNING));
- }
-
- /**
- * Create an http client to call the Kubernetes API.
- * This is based on the client creation in the official Kubernetes Java client.
- */
- private void initHttpClient() {
- try {
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init(getKeyStore());
- TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
-
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, trustManagers, new SecureRandom());
-
- httpClient = new OkHttpClient.Builder()
- .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0])
- .hostnameVerifier(OkHostnameVerifier.INSTANCE)
- .build();
- } catch (Exception e) {
- LOG.debug("Failed to create http client for Kubernetes API", e);
- }
- }
-
- private KeyStore getKeyStore() throws GeneralSecurityException, IOException {
- KeyStore caKeyStore = newEmptyKeyStore();
-
- try (FileInputStream fis = new FileInputStream(paths2.get(SERVICEACCOUNT_CA_PATH).toFile())) {
- CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
- Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(fis);
-
- int index = 0;
- for (Certificate certificate : certificates) {
- String certificateAlias = "ca" + index;
- caKeyStore.setCertificateEntry(certificateAlias, certificate);
- index++;
- }
- }
-
- return caKeyStore;
- }
-
- private static KeyStore newEmptyKeyStore() throws GeneralSecurityException, IOException {
- KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
- keyStore.load(null, null);
- return keyStore;
- }
-
- record VersionInfo(String major, String minor, String platform) {
- }
-
- private VersionInfo getVersionInfo() {
- try {
- Request request = buildRequest();
- try (Response response = httpClient.newCall(request).execute()) {
- ResponseBody responseBody = requireNonNull(response.body(), "Response body is null");
- return new Gson().fromJson(responseBody.string(), VersionInfo.class);
- }
- } catch (Exception e) {
- LOG.debug("Failed to get Kubernetes version info", e);
- return null;
- }
- }
-
- private Request buildRequest() throws URISyntaxException {
- String host = system2.envVariable(KUBERNETES_SERVICE_HOST);
- String port = system2.envVariable(KUBERNETES_SERVICE_PORT);
- if (host == null || port == null) {
- throw new IllegalStateException("Kubernetes environment variables are not set");
- }
-
- URI uri = new URI("https", null, host, Integer.parseInt(port), "/version", null, null);
-
- return new Request.Builder()
- .get()
- .url(uri.toString())
- .build();
- }
-
- @CheckForNull
- private String getKubernetesProvider() {
- try {
- Process process = processBuilderSupplier.get().command(KUBERNETES_PROVIDER_COMMAND).start();
- try (Scanner scanner = new Scanner(process.getInputStream(), UTF_8)) {
- scanner.useDelimiter("\n");
- // Null characters can be present in the output on Windows
- String output = scanner.next().replace("\u0000", "");
- return StringUtils.abbreviate(output, KUBERNETES_PROVIDER_MAX_SIZE);
- } finally {
- process.destroy();
- }
- } catch (Exception e) {
- LOG.debug("Failed to get Kubernetes provider", e);
- return null;
- }
- }
-
- @VisibleForTesting
- OkHttpClient getHttpClient() {
- return httpClient;
- }
-}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/QualityProfileDataProvider.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/QualityProfileDataProvider.java
deleted file mode 100644
index ed5fa907baf..00000000000
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/QualityProfileDataProvider.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.server.qualityprofile.QProfileComparison;
-
-import static java.util.stream.Collectors.toMap;
-
-public class QualityProfileDataProvider {
-
- private final DbClient dbClient;
- private final QProfileComparison qProfileComparison;
-
- public QualityProfileDataProvider(DbClient dbClient, QProfileComparison qProfileComparison) {
- this.dbClient = dbClient;
- this.qProfileComparison = qProfileComparison;
- }
-
- public List<TelemetryData.QualityProfile> retrieveQualityProfilesData() {
- try (DbSession dbSession = dbClient.openSession(false)) {
-
- Set<String> defaultProfileUuids = dbClient.qualityProfileDao().selectAllDefaultProfiles(dbSession)
- .stream().map(QProfileDto::getKee)
- .collect(Collectors.toSet());
-
- Map<String, QProfileDto> allProfileDtosByUuid = dbClient.qualityProfileDao().selectAll(dbSession)
- .stream()
- .collect(toMap(QProfileDto::getKee, p -> p));
-
- return allProfileDtosByUuid.entrySet().stream()
- .map(p -> mapQualityProfile(p.getValue(), allProfileDtosByUuid, defaultProfileUuids.contains(p.getKey()), dbSession))
- .toList();
- }
- }
-
- private TelemetryData.QualityProfile mapQualityProfile(QProfileDto profile, Map<String, QProfileDto> allProfileDtos, boolean isDefault, DbSession dbSession) {
- QProfileDto rootProfile = getRootProfile(profile.getKee(), allProfileDtos);
- Boolean isBuiltInRootParent;
- if (profile.isBuiltIn()) {
- isBuiltInRootParent = null;
- } else {
- isBuiltInRootParent = rootProfile.isBuiltIn() && !rootProfile.getKee().equals(profile.getKee());
- }
-
- Optional<QProfileComparison.QProfileComparisonResult> rulesComparison = Optional.of(profile)
- .filter(p -> isBuiltInRootParent != null && isBuiltInRootParent)
- .map(p -> qProfileComparison.compare(dbSession, rootProfile, profile));
-
- return new TelemetryData.QualityProfile(profile.getKee(),
- profile.getParentKee(),
- profile.getLanguage(),
- isDefault,
- profile.isBuiltIn(),
- isBuiltInRootParent,
- rulesComparison.map(c -> c.modified().size()).orElse(null),
- rulesComparison.map(c -> c.inRight().size()).orElse(null),
- rulesComparison.map(c -> c.inLeft().size()).orElse(null)
- );
- }
-
- public QProfileDto getRootProfile(String kee, Map<String, QProfileDto> allProfileDtos) {
- QProfileDto qProfileDto = allProfileDtos.get(kee);
- String parentKee = qProfileDto.getParentKee();
- if (parentKee != null) {
- return getRootProfile(parentKee, allProfileDtos);
- } else {
- return allProfileDtos.get(kee);
- }
- }
-}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryClient.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryClient.java
deleted file mode 100644
index 3163ea3c79f..00000000000
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryClient.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import java.io.IOException;
-import okhttp3.Call;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import okio.BufferedSink;
-import okio.GzipSink;
-import okio.Okio;
-import org.sonar.api.Startable;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.server.ServerSide;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_COMPRESSION;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-
-@ServerSide
-public class TelemetryClient implements Startable {
- private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
- private static final Logger LOG = LoggerFactory.getLogger(TelemetryClient.class);
-
- private final OkHttpClient okHttpClient;
- private final Configuration config;
- private String serverUrl;
- private boolean compression;
-
- public TelemetryClient(OkHttpClient okHttpClient, Configuration config) {
- this.config = config;
- this.okHttpClient = okHttpClient;
- }
-
- void upload(String json) throws IOException {
- Request request = buildHttpRequest(json);
- execute(okHttpClient.newCall(request));
- }
-
- void optOut(String json) {
- Request.Builder request = new Request.Builder();
- request.url(serverUrl);
- RequestBody body = RequestBody.create(JSON, json);
- request.delete(body);
-
- try {
- execute(okHttpClient.newCall(request.build()));
- } catch (IOException e) {
- LOG.debug("Error when sending opt-out usage statistics: {}", e.getMessage());
- }
- }
-
- private Request buildHttpRequest(String json) {
- Request.Builder request = new Request.Builder();
- request.addHeader("Content-Encoding", "gzip");
- request.addHeader("Content-Type", "application/json");
- request.url(serverUrl);
- RequestBody body = RequestBody.create(JSON, json);
- if (compression) {
- request.post(gzip(body));
- } else {
- request.post(body);
- }
- return request.build();
- }
-
- private static RequestBody gzip(final RequestBody body) {
- return new RequestBody() {
- @Override
- public MediaType contentType() {
- return body.contentType();
- }
-
- @Override
- public long contentLength() {
- // We don't know the compressed length in advance!
- return -1;
- }
-
- @Override
- public void writeTo(BufferedSink sink) throws IOException {
- BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
- body.writeTo(gzipSink);
- gzipSink.close();
- }
- };
- }
-
- private static void execute(Call call) throws IOException {
- try (Response ignored = call.execute()) {
- // auto close connection to avoid leaked connection
- }
- }
-
- @Override
- public void start() {
- this.serverUrl = config.get(SONAR_TELEMETRY_URL.getKey())
- .orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", SONAR_TELEMETRY_URL)));
- this.compression = config.getBoolean(SONAR_TELEMETRY_COMPRESSION.getKey()).orElse(true);
- }
-
- @Override
- public void stop() {
- // Nothing to do
- }
-}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDaemon.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDaemon.java
deleted file mode 100644
index 102c007eafb..00000000000
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDaemon.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.util.Optional;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.System2;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.server.property.InternalProperties;
-import org.sonar.server.util.AbstractStoppableScheduledExecutorServiceImpl;
-import org.sonar.server.util.GlobalLockManager;
-
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_ENABLE;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_FREQUENCY_IN_SECONDS;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-
-@ServerSide
-public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceImpl<ScheduledExecutorService> {
- private static final String THREAD_NAME_PREFIX = "sq-telemetry-service-";
- private static final int ONE_DAY = 24 * 60 * 60 * 1_000;
- private static final String I_PROP_LAST_PING = "telemetry.lastPing";
- private static final String I_PROP_OPT_OUT = "telemetry.optOut";
- private static final String LOCK_NAME = "TelemetryStat";
- private static final Logger LOG = LoggerFactory.getLogger(TelemetryDaemon.class);
- private static final String LOCK_DELAY_SEC = "sonar.telemetry.lock.delay";
- static final String I_PROP_MESSAGE_SEQUENCE = "telemetry.messageSeq";
-
- private final TelemetryDataLoader dataLoader;
- private final TelemetryDataJsonWriter dataJsonWriter;
- private final TelemetryClient telemetryClient;
- private final GlobalLockManager lockManager;
- private final Configuration config;
- private final InternalProperties internalProperties;
- private final System2 system2;
-
- public TelemetryDaemon(TelemetryDataLoader dataLoader, TelemetryDataJsonWriter dataJsonWriter, TelemetryClient telemetryClient, Configuration config,
- InternalProperties internalProperties, GlobalLockManager lockManager, System2 system2) {
- super(Executors.newSingleThreadScheduledExecutor(newThreadFactory()));
- this.dataLoader = dataLoader;
- this.dataJsonWriter = dataJsonWriter;
- this.telemetryClient = telemetryClient;
- this.config = config;
- this.internalProperties = internalProperties;
- this.lockManager = lockManager;
- this.system2 = system2;
- }
-
- @Override
- public void start() {
- boolean isTelemetryActivated = config.getBoolean(SONAR_TELEMETRY_ENABLE.getKey())
- .orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", SONAR_TELEMETRY_URL.getKey())));
- boolean hasOptOut = internalProperties.read(I_PROP_OPT_OUT).isPresent();
- if (!isTelemetryActivated && !hasOptOut) {
- optOut();
- internalProperties.write(I_PROP_OPT_OUT, String.valueOf(system2.now()));
- LOG.info("Sharing of SonarQube statistics is disabled.");
- }
- if (isTelemetryActivated && hasOptOut) {
- internalProperties.write(I_PROP_OPT_OUT, null);
- }
- if (!isTelemetryActivated) {
- return;
- }
- LOG.info("Sharing of SonarQube statistics is enabled.");
- int frequencyInSeconds = frequency();
- scheduleWithFixedDelay(telemetryCommand(), frequencyInSeconds, frequencyInSeconds, TimeUnit.SECONDS);
- }
-
- private static ThreadFactory newThreadFactory() {
- return new ThreadFactoryBuilder()
- .setNameFormat(THREAD_NAME_PREFIX + "%d")
- .setPriority(Thread.MIN_PRIORITY)
- .build();
- }
-
- private Runnable telemetryCommand() {
- return () -> {
- try {
-
- if (!lockManager.tryLock(LOCK_NAME, lockDuration())) {
- return;
- }
-
- long now = system2.now();
- if (shouldUploadStatistics(now)) {
- uploadStatistics();
- updateTelemetryProps(now);
- }
- } catch (Exception e) {
- LOG.debug("Error while checking SonarQube statistics: {}", e.getMessage(), e);
- }
- // do not check at start up to exclude test instance which are not up for a long time
- };
- }
-
- private void updateTelemetryProps(long now) {
- internalProperties.write(I_PROP_LAST_PING, String.valueOf(now));
-
- Optional<String> currentSequence = internalProperties.read(I_PROP_MESSAGE_SEQUENCE);
- if (currentSequence.isEmpty()) {
- internalProperties.write(I_PROP_MESSAGE_SEQUENCE, String.valueOf(1));
- return;
- }
-
- long current = Long.parseLong(currentSequence.get());
- internalProperties.write(I_PROP_MESSAGE_SEQUENCE, String.valueOf(current + 1));
- }
-
- private void optOut() {
- StringWriter json = new StringWriter();
- try (JsonWriter writer = JsonWriter.of(json)) {
- writer.beginObject();
- writer.prop("id", dataLoader.loadServerId());
- writer.endObject();
- }
- telemetryClient.optOut(json.toString());
- }
-
- private void uploadStatistics() throws IOException {
- TelemetryData statistics = dataLoader.load();
- StringWriter jsonString = new StringWriter();
- try (JsonWriter json = JsonWriter.of(jsonString)) {
- dataJsonWriter.writeTelemetryData(json, statistics);
- }
- telemetryClient.upload(jsonString.toString());
- dataLoader.reset();
- }
-
- private boolean shouldUploadStatistics(long now) {
- Optional<Long> lastPing = internalProperties.read(I_PROP_LAST_PING).map(Long::valueOf);
- return lastPing.isEmpty() || now - lastPing.get() >= ONE_DAY;
- }
-
- private int frequency() {
- return config.getInt(SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getKey())
- .orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", SONAR_TELEMETRY_FREQUENCY_IN_SECONDS)));
- }
-
- private int lockDuration() {
- return config.getInt(LOCK_DELAY_SEC).orElse(60);
- }
-}
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
deleted file mode 100644
index 728300033c9..00000000000
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import java.sql.DatabaseMetaData;
-import java.sql.SQLException;
-import java.util.ArrayList;
-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;
-import org.sonar.api.server.ServerSide;
-import org.sonar.core.platform.PlatformEditionProvider;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginRepository;
-import org.sonar.db.DbClient;
-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.BranchMeasuresDto;
-import org.sonar.db.component.PrBranchAnalyzedLanguageCountByProjectDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.measure.ProjectLocDistributionDto;
-import org.sonar.db.measure.ProjectMainBranchLiveMeasureDto;
-import org.sonar.db.metric.MetricDto;
-import org.sonar.db.newcodeperiod.NewCodePeriodDto;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.db.property.PropertyDto;
-import org.sonar.db.property.PropertyQuery;
-import org.sonar.db.qualitygate.ProjectQgateAssociationDto;
-import org.sonar.db.qualitygate.QualityGateConditionDto;
-import org.sonar.db.qualitygate.QualityGateDto;
-import org.sonar.server.management.ManagedInstanceService;
-import org.sonar.server.platform.ContainerSupport;
-import org.sonar.server.property.InternalProperties;
-import org.sonar.server.qualitygate.Condition;
-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;
-import static java.util.stream.Collectors.groupingBy;
-import static java.util.stream.Collectors.toMap;
-import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
-import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
-import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
-import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
-import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY;
-import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
-import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDCI;
-import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDSCM;
-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.qualitygate.Condition.Operator.fromDbValue;
-import static org.sonar.server.telemetry.TelemetryDaemon.I_PROP_MESSAGE_SEQUENCE;
-
-@ServerSide
-public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
- private static final String UNDETECTED = "undetected";
- public static final String EXTERNAL_SECURITY_REPORT_EXPORTED_AT = "project.externalSecurityReportExportedAt";
-
- private static final Map<String, String> LANGUAGES_BY_SECURITY_JSON_PROPERTY_MAP = Map.of(
- "sonar.security.config.javasecurity", "java",
- "sonar.security.config.phpsecurity", "php",
- "sonar.security.config.pythonsecurity", "python",
- "sonar.security.config.roslyn.sonaranalyzer.security.cs", "csharp");
-
- private final Server server;
- private final DbClient dbClient;
- private final PluginRepository pluginRepository;
- private final PlatformEditionProvider editionProvider;
- private final Configuration configuration;
- private final InternalProperties internalProperties;
- private final ContainerSupport containerSupport;
- private final QualityGateCaycChecker qualityGateCaycChecker;
- private final QualityGateFinder qualityGateFinder;
- private final ManagedInstanceService managedInstanceService;
- private final CloudUsageDataProvider cloudUsageDataProvider;
- private final QualityProfileDataProvider qualityProfileDataProvider;
- private final Set<NewCodeDefinition> newCodeDefinitions = new HashSet<>();
- private final Map<String, NewCodeDefinition> ncdByProject = new HashMap<>();
- private final Map<String, NewCodeDefinition> ncdByBranch = new HashMap<>();
- private final Map<String, String> defaultQualityProfileByLanguage = new HashMap<>();
- private final Map<ProjectLanguageKey, String> qualityProfileByProjectAndLanguage = new HashMap<>();
- private NewCodeDefinition instanceNcd = NewCodeDefinition.getInstanceDefault();
-
- @Inject
- public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository,
- PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration,
- ContainerSupport containerSupport, QualityGateCaycChecker qualityGateCaycChecker, QualityGateFinder qualityGateFinder,
- ManagedInstanceService managedInstanceService, CloudUsageDataProvider cloudUsageDataProvider, QualityProfileDataProvider qualityProfileDataProvider) {
- this.server = server;
- this.dbClient = dbClient;
- this.pluginRepository = pluginRepository;
- this.editionProvider = editionProvider;
- this.internalProperties = internalProperties;
- this.configuration = configuration;
- this.containerSupport = containerSupport;
- this.qualityGateCaycChecker = qualityGateCaycChecker;
- this.qualityGateFinder = qualityGateFinder;
- this.managedInstanceService = managedInstanceService;
- this.cloudUsageDataProvider = cloudUsageDataProvider;
- this.qualityProfileDataProvider = qualityProfileDataProvider;
- }
-
- private static Database loadDatabaseMetadata(DbSession dbSession) {
- try {
- DatabaseMetaData metadata = dbSession.getConnection().getMetaData();
- return new Database(metadata.getDatabaseProductName(), metadata.getDatabaseProductVersion());
- } catch (SQLException e) {
- throw new IllegalStateException("Fail to get DB metadata", e);
- }
- }
-
- @Override
- public TelemetryData load() {
- TelemetryData.Builder data = TelemetryData.builder();
-
- data.setMessageSequenceNumber(retrieveCurrentMessageSequenceNumber() + 1);
- data.setServerId(server.getId());
- data.setVersion(server.getVersion());
- data.setEdition(editionProvider.get().orElse(null));
- Function<PluginInfo, String> getVersion = plugin -> plugin.getVersion() == null ? "undefined" : plugin.getVersion().getName();
- Map<String, String> plugins = pluginRepository.getPluginInfos().stream().collect(toMap(PluginInfo::getKey, getVersion));
- data.setPlugins(plugins);
- try (DbSession dbSession = dbClient.openSession(false)) {
- var branchMeasuresDtos = dbClient.branchDao().selectBranchMeasuresWithCaycMetric(dbSession);
- loadNewCodeDefinitions(dbSession, branchMeasuresDtos);
- loadQualityProfiles(dbSession);
-
- data.setDatabase(loadDatabaseMetadata(dbSession));
- data.setNcdId(instanceNcd.hashCode());
- data.setNewCodeDefinitions(newCodeDefinitions);
-
- String defaultQualityGateUuid = qualityGateFinder.getDefault(dbSession).getUuid();
- String sonarWayQualityGateUuid = qualityGateFinder.getSonarWay(dbSession).getUuid();
- List<ProjectDto> projects = dbClient.projectDao().selectProjects(dbSession);
-
- data.setDefaultQualityGate(defaultQualityGateUuid);
- data.setSonarWayQualityGate(sonarWayQualityGateUuid);
- resolveUnanalyzedLanguageCode(data, dbSession);
- resolveProjectStatistics(data, dbSession, defaultQualityGateUuid, projects);
- resolveProjects(data, dbSession);
- resolveBranches(data, branchMeasuresDtos);
- resolveQualityGates(data, dbSession);
- resolveUsers(data, dbSession);
- }
-
- data.setQualityProfiles(qualityProfileDataProvider.retrieveQualityProfilesData());
-
- setSecurityCustomConfigIfPresent(data);
-
- Optional<String> installationDateProperty = internalProperties.read(InternalProperties.INSTALLATION_DATE);
- installationDateProperty.ifPresent(s -> data.setInstallationDate(Long.valueOf(s)));
- Optional<String> installationVersionProperty = internalProperties.read(InternalProperties.INSTALLATION_VERSION);
-
- return data
- .setInstallationVersion(installationVersionProperty.orElse(null))
- .setInContainer(containerSupport.isRunningInContainer())
- .setManagedInstanceInformation(buildManagedInstanceInformation())
- .setCloudUsage(buildCloudUsage())
- .build();
- }
-
- private void resolveBranches(TelemetryData.Builder data, List<BranchMeasuresDto> branchMeasuresDtos) {
- var branches = branchMeasuresDtos.stream()
- .map(dto -> {
- var projectNcd = ncdByProject.getOrDefault(dto.getProjectUuid(), instanceNcd);
- var ncdId = ncdByBranch.getOrDefault(dto.getBranchUuid(), projectNcd).hashCode();
- return new TelemetryData.Branch(
- dto.getProjectUuid(), dto.getBranchUuid(), ncdId,
- dto.getGreenQualityGateCount(), dto.getAnalysisCount(), dto.getExcludeFromPurge());
- })
- .toList();
- data.setBranches(branches);
- }
-
- @Override
- public void reset() {
- this.newCodeDefinitions.clear();
- this.ncdByBranch.clear();
- this.ncdByProject.clear();
- this.instanceNcd = NewCodeDefinition.getInstanceDefault();
- this.defaultQualityProfileByLanguage.clear();
- this.qualityProfileByProjectAndLanguage.clear();
- }
-
- private void loadNewCodeDefinitions(DbSession dbSession, List<BranchMeasuresDto> branchMeasuresDtos) {
- var branchUuidByKey = branchMeasuresDtos.stream()
- .collect(Collectors.toMap(dto -> createBranchUniqueKey(dto.getProjectUuid(), dto.getBranchKey()), BranchMeasuresDto::getBranchUuid));
- 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 || isCommunityEdition()) {
- 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 void loadQualityProfiles(DbSession dbSession) {
- dbClient.qualityProfileDao().selectAllDefaultProfiles(dbSession)
- .forEach(defaultQualityProfile -> this.defaultQualityProfileByLanguage.put(defaultQualityProfile.getLanguage(), defaultQualityProfile.getKee()));
-
- dbClient.qualityProfileDao().selectAllProjectAssociations(dbSession)
- .forEach(projectAssociation -> qualityProfileByProjectAndLanguage.put(
- new ProjectLanguageKey(projectAssociation.projectUuid(), projectAssociation.language()),
- projectAssociation.profileKey()));
- }
-
- private boolean isCommunityEdition() {
- var edition = editionProvider.get();
- return edition.isPresent() && edition.get() == COMMUNITY;
- }
-
- 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);
- long numberOfUnanalyzedCppMeasures = dbClient.liveMeasureDao().countProjectsHavingMeasure(dbSession, UNANALYZED_CPP_KEY);
- editionProvider.get()
- .filter(edition -> edition.equals(COMMUNITY))
- .ifPresent(edition -> {
- data.setHasUnanalyzedC(numberOfUnanalyzedCMeasures > 0);
- data.setHasUnanalyzedCpp(numberOfUnanalyzedCppMeasures > 0);
- });
- }
-
- private Long retrieveCurrentMessageSequenceNumber() {
- return internalProperties.read(I_PROP_MESSAGE_SEQUENCE).map(Long::parseLong).orElse(0L);
- }
-
- private void resolveProjectStatistics(TelemetryData.Builder data, DbSession dbSession, String defaultQualityGateUuid, List<ProjectDto> projects) {
- Map<String, String> scmByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDSCM);
- Map<String, String> ciByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDCI);
- Map<String, ProjectAlmKeyAndProject> almAndUrlAndMonorepoByProject = getAlmAndUrlByProject(dbSession);
- Map<String, PrBranchAnalyzedLanguageCountByProjectDto> prAndBranchCountByProject = dbClient.branchDao().countPrBranchAnalyzedLanguageByProjectUuid(dbSession)
- .stream().collect(toMap(PrBranchAnalyzedLanguageCountByProjectDto::getProjectUuid, Function.identity()));
- Map<String, String> qgatesByProject = getProjectQgatesMap(dbSession);
- Map<String, Map<String, Number>> metricsByProject = getProjectMetricsByMetricKeys(dbSession, TECHNICAL_DEBT_KEY, DEVELOPMENT_COST_KEY, SECURITY_HOTSPOTS_KEY,
- VULNERABILITIES_KEY,
- BUGS_KEY);
- Map<String, Long> securityReportExportedAtByProjectUuid = getSecurityReportExportedAtDateByProjectUuid(dbSession);
-
- List<TelemetryData.ProjectStatistics> projectStatistics = new ArrayList<>();
- for (ProjectDto project : projects) {
- String projectUuid = project.getUuid();
- Map<String, Number> metrics = metricsByProject.getOrDefault(projectUuid, Collections.emptyMap());
- Optional<PrBranchAnalyzedLanguageCountByProjectDto> counts = ofNullable(prAndBranchCountByProject.get(projectUuid));
-
- TelemetryData.ProjectStatistics stats = new TelemetryData.ProjectStatistics.Builder()
- .setProjectUuid(projectUuid)
- .setBranchCount(counts.map(PrBranchAnalyzedLanguageCountByProjectDto::getBranch).orElse(0L))
- .setPRCount(counts.map(PrBranchAnalyzedLanguageCountByProjectDto::getPullRequest).orElse(0L))
- .setQG(qgatesByProject.getOrDefault(projectUuid, defaultQualityGateUuid))
- .setScm(Optional.ofNullable(scmByProject.get(projectUuid)).orElse(UNDETECTED))
- .setCi(Optional.ofNullable(ciByProject.get(projectUuid)).orElse(UNDETECTED))
- .setDevops(resolveDevopsPlatform(almAndUrlAndMonorepoByProject, projectUuid))
- .setBugs(metrics.getOrDefault("bugs", null))
- .setDevelopmentCost(metrics.getOrDefault("development_cost", null))
- .setVulnerabilities(metrics.getOrDefault("vulnerabilities", null))
- .setSecurityHotspots(metrics.getOrDefault("security_hotspots", null))
- .setTechnicalDebt(metrics.getOrDefault("sqale_index", null))
- .setNcdId(ncdByProject.getOrDefault(projectUuid, instanceNcd).hashCode())
- .setExternalSecurityReportExportedAt(securityReportExportedAtByProjectUuid.get(projectUuid))
- .setCreationMethod(project.getCreationMethod())
- .setMonorepo(resolveMonorepo(almAndUrlAndMonorepoByProject, projectUuid))
- .build();
- projectStatistics.add(stats);
- }
- data.setProjectStatistics(projectStatistics);
- }
-
- private Map<String, Long> getSecurityReportExportedAtDateByProjectUuid(DbSession dbSession) {
- PropertyQuery propertyQuery = PropertyQuery.builder().setKey(EXTERNAL_SECURITY_REPORT_EXPORTED_AT).build();
- List<PropertyDto> properties = dbClient.propertiesDao().selectByQuery(propertyQuery, dbSession);
- return properties.stream()
- .collect(toMap(PropertyDto::getEntityUuid, propertyDto -> Long.parseLong(propertyDto.getValue())));
- }
-
- private static String resolveDevopsPlatform(Map<String, ProjectAlmKeyAndProject> almAndUrlByProject, String projectUuid) {
- if (almAndUrlByProject.containsKey(projectUuid)) {
- ProjectAlmKeyAndProject projectAlmKeyAndProject = almAndUrlByProject.get(projectUuid);
- return getAlmName(projectAlmKeyAndProject.getAlmId(), projectAlmKeyAndProject.getUrl());
- }
- return UNDETECTED;
- }
-
- private static Boolean resolveMonorepo(Map<String, ProjectAlmKeyAndProject> almAndUrlByProject, String projectUuid) {
- return Optional.ofNullable(almAndUrlByProject.get(projectUuid))
- .map(ProjectAlmKeyAndProject::getMonorepo)
- .orElse(false);
- }
-
- private void resolveProjects(TelemetryData.Builder data, DbSession dbSession) {
- Map<String, String> metricUuidMap = getNclocMetricUuidMap(dbSession);
- String nclocUuid = metricUuidMap.get(NCLOC_KEY);
- String nclocDistributionUuid = metricUuidMap.get(NCLOC_LANGUAGE_DISTRIBUTION_KEY);
- List<ProjectLocDistributionDto> branchesWithLargestNcloc = dbClient.liveMeasureDao().selectLargestBranchesLocDistribution(dbSession, nclocUuid, nclocDistributionUuid);
- List<String> branchUuids = branchesWithLargestNcloc.stream().map(ProjectLocDistributionDto::branchUuid).toList();
- Map<String, Long> latestSnapshotMap = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, branchUuids)
- .stream()
- .collect(toMap(SnapshotDto::getRootComponentUuid, SnapshotDto::getAnalysisDate));
- data.setProjects(buildProjectsList(branchesWithLargestNcloc, latestSnapshotMap));
- }
-
- private List<TelemetryData.Project> buildProjectsList(List<ProjectLocDistributionDto> branchesWithLargestNcloc, Map<String, Long> latestSnapshotMap) {
- return branchesWithLargestNcloc.stream()
- .flatMap(measure -> Arrays.stream(measure.locDistribution().split(";"))
- .map(languageAndLoc -> languageAndLoc.split("="))
- .map(languageAndLoc -> new TelemetryData.Project(
- measure.projectUuid(),
- latestSnapshotMap.get(measure.branchUuid()),
- languageAndLoc[0],
- getQualityProfile(measure.projectUuid(), languageAndLoc[0]),
- Long.parseLong(languageAndLoc[1]))))
- .toList();
- }
-
- private String getQualityProfile(String projectUuid, String language) {
- String qualityProfile = this.qualityProfileByProjectAndLanguage.get(new ProjectLanguageKey(projectUuid, language));
- if (qualityProfile != null) {
- return qualityProfile;
- }
- return this.defaultQualityProfileByLanguage.get(language);
- }
-
- private Map<String, String> getNclocMetricUuidMap(DbSession dbSession) {
- return dbClient.metricDao().selectByKeys(dbSession, asList(NCLOC_KEY, NCLOC_LANGUAGE_DISTRIBUTION_KEY))
- .stream()
- .collect(toMap(MetricDto::getKey, MetricDto::getUuid));
- }
-
- private void resolveQualityGates(TelemetryData.Builder data, DbSession dbSession) {
- List<TelemetryData.QualityGate> qualityGates = new ArrayList<>();
- Collection<QualityGateDto> qualityGateDtos = dbClient.qualityGateDao().selectAll(dbSession);
- Collection<QualityGateConditionDto> qualityGateConditions = dbClient.gateConditionDao().selectAll(dbSession);
- Map<String, MetricDto> metricsByUuid = getMetricsByUuid(dbSession, qualityGateConditions);
-
- Map<String, List<Condition>> conditionsMap = mapQualityGateConditions(qualityGateConditions, metricsByUuid);
-
- for (QualityGateDto qualityGateDto : qualityGateDtos) {
- String qualityGateUuid = qualityGateDto.getUuid();
- List<Condition> conditions = conditionsMap.getOrDefault(qualityGateUuid, Collections.emptyList());
- qualityGates.add(
- new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession,
- qualityGateDto.getUuid()).toString(), conditions));
- }
-
- data.setQualityGates(qualityGates);
- }
-
- private static Map<String, List<Condition>> mapQualityGateConditions(Collection<QualityGateConditionDto> qualityGateConditions, Map<String, MetricDto> metricsByUuid) {
- Map<String, List<Condition>> conditionsMap = new HashMap<>();
-
- for (QualityGateConditionDto condition : qualityGateConditions) {
- String qualityGateUuid = condition.getQualityGateUuid();
-
- MetricDto metricDto = metricsByUuid.get(condition.getMetricUuid());
- String metricKey = metricDto != null ? metricDto.getKey() : "Unknown Metric";
-
- Condition telemetryCondition = new Condition(
- metricKey,
- fromDbValue(condition.getOperator()),
- condition.getErrorThreshold());
-
- conditionsMap
- .computeIfAbsent(qualityGateUuid, k -> new ArrayList<>())
- .add(telemetryCondition);
- }
-
- return conditionsMap;
- }
-
- private Map<String, MetricDto> getMetricsByUuid(DbSession dbSession, Collection<QualityGateConditionDto> conditions) {
- Set<String> metricUuids = conditions.stream().map(QualityGateConditionDto::getMetricUuid).collect(Collectors.toSet());
- return dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream().filter(MetricDto::isEnabled).collect(Collectors.toMap(MetricDto::getUuid, Function.identity()));
- }
-
- private void resolveUsers(TelemetryData.Builder data, DbSession dbSession) {
- data.setUsers(dbClient.userDao().selectUsersForTelemetry(dbSession));
- }
-
- private void setSecurityCustomConfigIfPresent(TelemetryData.Builder data) {
- editionProvider.get()
- .filter(edition -> asList(ENTERPRISE, DATACENTER).contains(edition))
- .ifPresent(edition -> data.setCustomSecurityConfigs(getCustomerSecurityConfigurations()));
- }
-
- private Map<String, String> getAnalysisPropertyByProject(DbSession dbSession, String analysisPropertyKey) {
- return dbClient.analysisPropertiesDao()
- .selectAnalysisPropertyValueInLastAnalysisPerProject(dbSession, analysisPropertyKey)
- .stream()
- .collect(toMap(AnalysisPropertyValuePerProject::getProjectUuid, AnalysisPropertyValuePerProject::getPropertyValue));
- }
-
- private Map<String, ProjectAlmKeyAndProject> getAlmAndUrlByProject(DbSession dbSession) {
- List<ProjectAlmKeyAndProject> projectAlmKeyAndProjects = dbClient.projectAlmSettingDao().selectAlmTypeAndUrlByProject(dbSession);
- return projectAlmKeyAndProjects.stream().collect(toMap(ProjectAlmKeyAndProject::getProjectUuid, Function.identity()));
- }
-
- private static String getAlmName(String alm, String url) {
- if (checkIfCloudAlm(alm, ALM.GITHUB.getId(), url, "https://api.github.com")) {
- return "github_cloud";
- }
-
- if (checkIfCloudAlm(alm, ALM.GITLAB.getId(), url, "https://gitlab.com/api/v4")) {
- return "gitlab_cloud";
- }
-
- if (checkIfCloudAlm(alm, ALM.AZURE_DEVOPS.getId(), url, "https://dev.azure.com")) {
- return "azure_devops_cloud";
- }
-
- if (ALM.BITBUCKET_CLOUD.getId().equals(alm)) {
- return alm;
- }
-
- return alm + "_server";
- }
-
- private Map<String, String> getProjectQgatesMap(DbSession dbSession) {
- return dbClient.projectQgateAssociationDao().selectAll(dbSession)
- .stream()
- .collect(toMap(ProjectQgateAssociationDto::getUuid, p -> Optional.ofNullable(p.getGateUuid()).orElse("")));
- }
-
- private Map<String, Map<String, Number>> getProjectMetricsByMetricKeys(DbSession dbSession, String... metricKeys) {
- Map<String, String> metricNamesByUuid = dbClient.metricDao().selectByKeys(dbSession, asList(metricKeys))
- .stream()
- .collect(toMap(MetricDto::getUuid, MetricDto::getKey));
-
- // metrics can be empty for un-analyzed projects
- if (metricNamesByUuid.isEmpty()) {
- return Collections.emptyMap();
- }
-
- return dbClient.liveMeasureDao().selectForProjectMainBranchesByMetricUuids(dbSession, metricNamesByUuid.keySet())
- .stream()
- .collect(groupingBy(ProjectMainBranchLiveMeasureDto::getProjectUuid,
- toMap(lmDto -> metricNamesByUuid.get(lmDto.getMetricUuid()),
- lmDto -> Optional.ofNullable(lmDto.getValue()).orElseGet(() -> Double.valueOf(lmDto.getTextValue())),
- (oldValue, newValue) -> newValue, HashMap::new)));
- }
-
- private static boolean checkIfCloudAlm(String almRaw, String alm, String url, String cloudUrl) {
- return alm.equals(almRaw) && startsWithIgnoreCase(url, cloudUrl);
- }
-
- @Override
- public String loadServerId() {
- return server.getId();
- }
-
- private Set<String> getCustomerSecurityConfigurations() {
- return LANGUAGES_BY_SECURITY_JSON_PROPERTY_MAP.keySet().stream()
- .filter(this::isPropertyPresentInConfiguration)
- .map(LANGUAGES_BY_SECURITY_JSON_PROPERTY_MAP::get)
- .collect(Collectors.toSet());
- }
-
- private boolean isPropertyPresentInConfiguration(String property) {
- return configuration.get(property).isPresent();
- }
-
- private TelemetryData.ManagedInstanceInformation buildManagedInstanceInformation() {
- String provider = managedInstanceService.isInstanceExternallyManaged() ? managedInstanceService.getProviderName() : null;
- return new TelemetryData.ManagedInstanceInformation(managedInstanceService.isInstanceExternallyManaged(), provider);
- }
-
- private TelemetryData.CloudUsage buildCloudUsage() {
- return cloudUsageDataProvider.getCloudUsage();
- }
-
- private record ProjectLanguageKey(String projectKey, String language) {
- }
-}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/package-info.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/package-info.java
deleted file mode 100644
index 63923469e26..00000000000
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/package-info.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.telemetry;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/CloudUsageDataProviderTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/CloudUsageDataProviderTest.java
deleted file mode 100644
index 3ec1d0e1582..00000000000
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/CloudUsageDataProviderTest.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.nio.file.Paths;
-import javax.annotation.Nullable;
-import okhttp3.Call;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Protocol;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.sonar.api.utils.System2;
-import org.sonar.server.platform.ContainerSupport;
-import org.sonar.server.util.Paths2;
-import org.sonarqube.ws.MediaTypes;
-
-import static java.util.Objects.requireNonNull;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.server.telemetry.CloudUsageDataProvider.DOCKER_RUNNING;
-import static org.sonar.server.telemetry.CloudUsageDataProvider.KUBERNETES_SERVICE_HOST;
-import static org.sonar.server.telemetry.CloudUsageDataProvider.KUBERNETES_SERVICE_PORT;
-import static org.sonar.server.telemetry.CloudUsageDataProvider.SONAR_HELM_CHART_VERSION;
-
-public class CloudUsageDataProviderTest {
-
- private final System2 system2 = Mockito.mock(System2.class);
- private final Paths2 paths2 = Mockito.mock(Paths2.class);
- private final OkHttpClient httpClient = Mockito.mock(OkHttpClient.class);
- private final ContainerSupport containerSupport = mock(ContainerSupport.class);
- private final ProcessBuilder processBuilder = mock(ProcessBuilder.class);
- private final CloudUsageDataProvider underTest = new CloudUsageDataProvider(containerSupport, system2, paths2, () -> processBuilder,
- httpClient);
-
- @Before
- public void setUp() throws Exception {
- when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn("localhost");
- when(system2.envVariable(KUBERNETES_SERVICE_PORT)).thenReturn("443");
-
- mockHttpClientCall(200, "OK", ResponseBody.create("""
- {
- "major": "1",
- "minor": "25",
- "gitVersion": "v1.25.3",
- "gitCommit": "434bfd82814af038ad94d62ebe59b133fcb50506",
- "gitTreeState": "clean",
- "buildDate": "2022-11-02T03:24:50Z",
- "goVersion": "go1.19.2",
- "compiler": "gc",
- "platform": "linux/arm64"
- }
- """, MediaType.parse(MediaTypes.JSON)));
- }
-
- private void mockHttpClientCall(int code, String message, @Nullable ResponseBody body) throws IOException {
- Call callMock = mock(Call.class);
- when(callMock.execute()).thenReturn(new Response.Builder()
- .request(new Request.Builder().url("http://any.test/").build())
- .protocol(Protocol.HTTP_1_1)
- .code(code)
- .message(message)
- .body(body)
- .build());
- when(httpClient.newCall(any())).thenReturn(callMock);
- }
-
- @Test
- public void containerRuntime_whenContainerSupportContextExists_shouldNotBeNull() {
- when(containerSupport.getContainerContext()).thenReturn("docker");
- assertThat(underTest.getCloudUsage().containerRuntime()).isEqualTo("docker");
- }
-
- @Test
- public void containerRuntime_whenContainerSupportContextMissing_shouldBeNull() {
- when(containerSupport.getContainerContext()).thenReturn(null);
- assertThat(underTest.getCloudUsage().containerRuntime()).isNull();
- }
-
- @Test
- public void kubernetes_whenEnvVarExists_shouldReturnTrue() {
- assertThat(underTest.getCloudUsage().kubernetes()).isTrue();
- }
-
- @Test
- public void kubernetes_whenEnvVarDoesNotExist_shouldReturnFalse() {
- when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn(null);
- assertThat(underTest.getCloudUsage().kubernetes()).isFalse();
- }
-
- @Test
- public void kubernetesVersion_whenOnKubernetes_shouldReturnValue() {
- assertThat(underTest.getCloudUsage().kubernetesVersion()).isEqualTo("1.25");
- }
-
- @Test
- public void kubernetesVersion_whenNotOnKubernetes_shouldReturnNull() {
- when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn(null);
- assertThat(underTest.getCloudUsage().kubernetesVersion()).isNull();
- }
-
- @Test
- public void kubernetesVersion_whenApiCallFails_shouldReturnNull() throws IOException {
- mockHttpClientCall(404, "not found", null);
- assertThat(underTest.getCloudUsage().kubernetesVersion()).isNull();
- }
-
- @Test
- public void kubernetesPlatform_whenOnKubernetes_shouldReturnValue() {
- assertThat(underTest.getCloudUsage().kubernetesPlatform()).isEqualTo("linux/arm64");
- }
-
- @Test
- public void kubernetesPlatform_whenNotOnKubernetes_shouldReturnNull() {
- when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn(null);
- assertThat(underTest.getCloudUsage().kubernetesPlatform()).isNull();
- }
-
- @Test
- public void kubernetesPlatform_whenApiCallFails_shouldReturnNull() throws IOException {
- mockHttpClientCall(404, "not found", null);
- assertThat(underTest.getCloudUsage().kubernetesPlatform()).isNull();
- }
-
- @Test
- public void kubernetesProvider_shouldReturnValue() throws IOException {
- Process processMock = mock(Process.class);
- when(processMock.getInputStream()).thenReturn(new ByteArrayInputStream("some-provider".getBytes()));
- when(processBuilder.command(any(String[].class))).thenReturn(processBuilder);
- when(processBuilder.start()).thenReturn(processMock);
-
- assertThat(underTest.getCloudUsage().kubernetesProvider()).isEqualTo("some-provider");
- }
-
- @Test
- public void kubernetesProvider_whenValueContainsNullChars_shouldReturnValueWithoutNullChars() throws IOException {
- Process processMock = mock(Process.class);
- when(processMock.getInputStream()).thenReturn(new ByteArrayInputStream("so\u0000me-prov\u0000ider".getBytes()));
- when(processBuilder.command(any(String[].class))).thenReturn(processBuilder);
- when(processBuilder.start()).thenReturn(processMock);
-
- assertThat(underTest.getCloudUsage().kubernetesProvider()).isEqualTo("some-provider");
- }
-
- @Test
- public void officialHelmChart_whenEnvVarExists_shouldReturnValue() {
- when(system2.envVariable(SONAR_HELM_CHART_VERSION)).thenReturn("10.1.0");
- assertThat(underTest.getCloudUsage().officialHelmChart()).isEqualTo("10.1.0");
- }
-
- @Test
- public void officialHelmChart_whenEnvVarDoesNotExist_shouldReturnNull() {
- when(system2.envVariable(SONAR_HELM_CHART_VERSION)).thenReturn(null);
- assertThat(underTest.getCloudUsage().officialHelmChart()).isNull();
- }
-
- @Test
- public void officialImage_whenEnvVarTrue_shouldReturnTrue() {
- when(system2.envVariable(DOCKER_RUNNING)).thenReturn("True");
- assertThat(underTest.getCloudUsage().officialImage()).isTrue();
- }
-
- @Test
- public void officialImage_whenEnvVarFalse_shouldReturnFalse() {
- when(system2.envVariable(DOCKER_RUNNING)).thenReturn("False");
- assertThat(underTest.getCloudUsage().officialImage()).isFalse();
- }
-
- @Test
- public void officialImage_whenEnvVarDoesNotExist_shouldReturnFalse() {
- when(system2.envVariable(DOCKER_RUNNING)).thenReturn(null);
- assertThat(underTest.getCloudUsage().officialImage()).isFalse();
- }
-
- @Test
- public void initHttpClient_whenValidCertificate_shouldCreateClient() throws URISyntaxException {
- when(paths2.get(anyString())).thenReturn(Paths.get(requireNonNull(getClass().getResource("dummy.crt")).toURI()));
-
- CloudUsageDataProvider provider = new CloudUsageDataProvider(containerSupport, system2, paths2);
- assertThat(provider.getHttpClient()).isNotNull();
- }
-
- @Test
- public void initHttpClient_whenNotOnKubernetes_shouldNotCreateClient() throws URISyntaxException {
- when(paths2.get(anyString())).thenReturn(Paths.get(requireNonNull(getClass().getResource("dummy.crt")).toURI()));
- when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn(null);
-
- CloudUsageDataProvider provider = new CloudUsageDataProvider(containerSupport, system2, paths2);
- assertThat(provider.getHttpClient()).isNull();
- }
-
- @Test
- public void initHttpClient_whenCertificateNotFound_shouldFail() {
- when(paths2.get(any())).thenReturn(Paths.get("dummy.crt"));
-
- CloudUsageDataProvider provider = new CloudUsageDataProvider(containerSupport, system2, paths2);
- assertThat(provider.getHttpClient()).isNull();
- }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/FakeServer.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/FakeServer.java
deleted file mode 100644
index 211bbc6836e..00000000000
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/FakeServer.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import java.util.Date;
-import javax.annotation.CheckForNull;
-import org.sonar.api.platform.Server;
-
-import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
-
-class FakeServer extends Server {
- private String id;
- private String version;
-
- public FakeServer() {
- this.id = randomAlphanumeric(20);
- this.version = randomAlphanumeric(10);
- }
-
- @Override
- public String getId() {
- return id;
- }
-
- FakeServer setId(String id) {
- this.id = id;
- return this;
- }
-
- @Override
- public String getVersion() {
- return this.version;
- }
-
- public FakeServer setVersion(String version) {
- this.version = version;
- return this;
- }
-
- @Override
- public Date getStartedAt() {
- return null;
- }
-
- @Override
- public String getContextPath() {
- return null;
- }
-
- @Override
- public String getPublicRootUrl() {
- return null;
- }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientCompressionTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientCompressionTest.java
deleted file mode 100644
index da6891ddd0d..00000000000
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientCompressionTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import java.io.IOException;
-import java.util.Objects;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import okio.GzipSource;
-import okio.Okio;
-import org.assertj.core.api.Assertions;
-import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-
-public class TelemetryClientCompressionTest {
-
- private final OkHttpClient okHttpClient = new OkHttpClient();
- private final MockWebServer telemetryServer = new MockWebServer();
-
- @Test
- public void payload_is_gzip_encoded() throws IOException, InterruptedException {
- telemetryServer.enqueue(new MockResponse().setResponseCode(200));
- MapSettings settings = new MapSettings();
- settings.setProperty(SONAR_TELEMETRY_URL.getKey(), telemetryServer.url("/").toString());
- TelemetryClient underTest = new TelemetryClient(okHttpClient, settings.asConfig());
- underTest.start();
- underTest.upload("payload compressed with gzip");
-
- RecordedRequest request = telemetryServer.takeRequest();
-
- String contentType = Objects.requireNonNull(request.getHeader("content-type"));
- assertThat(MediaType.parse(contentType)).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
- assertThat(request.getHeader("content-encoding")).isEqualTo("gzip");
-
- GzipSource source = new GzipSource(request.getBody());
- String body = Okio.buffer(source).readUtf8();
- Assertions.assertThat(body).isEqualTo("payload compressed with gzip");
- }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientTest.java
deleted file mode 100644
index 95979c53ee3..00000000000
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import java.io.IOException;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okio.Buffer;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.config.internal.MapSettings;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_COMPRESSION;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-
-public class TelemetryClientTest {
-
- private static final String JSON = "{\"key\":\"value\"}";
- private static final String TELEMETRY_URL = "https://telemetry.com/url";
-
- private OkHttpClient okHttpClient = mock(OkHttpClient.class, RETURNS_DEEP_STUBS);
- private MapSettings settings = new MapSettings();
-
- private TelemetryClient underTest = new TelemetryClient(okHttpClient, settings.asConfig());
-
- @Test
- public void upload() throws IOException {
- ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
- settings.setProperty(SONAR_TELEMETRY_URL.getKey(), TELEMETRY_URL);
- settings.setProperty(SONAR_TELEMETRY_COMPRESSION.getKey(), false);
- underTest.start();
-
- underTest.upload(JSON);
-
- verify(okHttpClient).newCall(requestCaptor.capture());
- Request request = requestCaptor.getValue();
- assertThat(request.method()).isEqualTo("POST");
- assertThat(request.body().contentType()).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
- Buffer body = new Buffer();
- request.body().writeTo(body);
- assertThat(body.readUtf8()).isEqualTo(JSON);
- assertThat(request.url()).hasToString(TELEMETRY_URL);
- }
-
- @Test
- public void opt_out() throws IOException {
- ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
- settings.setProperty(SONAR_TELEMETRY_URL.getKey(), TELEMETRY_URL);
- underTest.start();
-
- underTest.optOut(JSON);
-
- verify(okHttpClient).newCall(requestCaptor.capture());
- Request request = requestCaptor.getValue();
- assertThat(request.method()).isEqualTo("DELETE");
- assertThat(request.body().contentType()).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
- Buffer body = new Buffer();
- request.body().writeTo(body);
- assertThat(body.readUtf8()).isEqualTo(JSON);
- assertThat(request.url()).hasToString(TELEMETRY_URL);
- }
-}
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
deleted file mode 100644
index d3b7a73ce41..00000000000
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import java.io.IOException;
-import java.util.Collections;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.slf4j.event.Level;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.testfixtures.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.server.property.InternalProperties;
-import org.sonar.server.property.MapInternalProperties;
-import org.sonar.server.util.GlobalLockManager;
-import org.sonar.server.util.GlobalLockManagerImpl;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.utils.DateUtils.parseDate;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_ENABLE;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_FREQUENCY_IN_SECONDS;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-
-public class TelemetryDaemonTest {
- @Rule
- public LogTester logger = new LogTester().setLevel(LoggerLevel.DEBUG);
-
- private static final long ONE_HOUR = 60 * 60 * 1_000L;
- private static final long ONE_DAY = 24 * ONE_HOUR;
- private static final TelemetryData SOME_TELEMETRY_DATA = TelemetryData.builder()
- .setServerId("foo")
- .setVersion("bar")
- .setMessageSequenceNumber(1L)
- .setPlugins(Collections.emptyMap())
- .setDatabase(new TelemetryData.Database("H2", "11"))
- .build();
-
- private final TelemetryClient client = mock(TelemetryClient.class);
- private final InternalProperties internalProperties = spy(new MapInternalProperties());
- private final GlobalLockManager lockManager = mock(GlobalLockManagerImpl.class);
- 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 final TelemetryDaemon underTest = new TelemetryDaemon(dataLoader, dataJsonWriter, client, settings.asConfig(), internalProperties, lockManager, system2);
-
- @After
- public void tearDown() {
- underTest.stop();
- }
-
- @Test
- public void send_data_via_client_at_startup_after_initial_delay() throws IOException {
- initTelemetrySettingsToDefaultValues();
- when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
- settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
- when(dataLoader.load()).thenReturn(SOME_TELEMETRY_DATA);
- mockDataJsonWriterDoingSomething();
-
- underTest.start();
-
- verify(client, timeout(4_000).atLeastOnce()).upload(anyString());
- verify(dataJsonWriter).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA));
- }
-
- private void mockDataJsonWriterDoingSomething() {
- doAnswer(t -> {
- JsonWriter json = t.getArgument(0);
- json.beginObject().prop("foo", "bar").endObject();
- return null;
- })
- .when(dataJsonWriter)
- .writeTelemetryData(any(), any());
- }
-
- @Test
- public void check_if_should_send_data_periodically() throws IOException {
- initTelemetrySettingsToDefaultValues();
- when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
- long now = system2.now();
- long twentyHoursAgo = now - (ONE_HOUR * 20L);
- long oneDayAgo = now - ONE_DAY;
- internalProperties.write("telemetry.lastPing", String.valueOf(twentyHoursAgo));
- settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
- when(dataLoader.load()).thenReturn(SOME_TELEMETRY_DATA);
- mockDataJsonWriterDoingSomething();
-
- underTest.start();
-
- verify(dataJsonWriter, after(2_000).never()).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA));
- verify(client, never()).upload(anyString());
-
- internalProperties.write("telemetry.lastPing", String.valueOf(oneDayAgo));
-
- verify(client, timeout(2_000)).upload(anyString());
- verify(dataJsonWriter).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA));
- }
-
- @Test
- public void do_not_send_data_if_last_ping_earlier_than_one_day_ago() throws IOException {
- initTelemetrySettingsToDefaultValues();
- when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
- settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
- long now = system2.now();
- long twentyHoursAgo = now - (ONE_HOUR * 20L);
- mockDataJsonWriterDoingSomething();
-
- internalProperties.write("telemetry.lastPing", String.valueOf(twentyHoursAgo));
- underTest.start();
-
- verify(client, after(2_000).never()).upload(anyString());
- }
-
- @Test
- public void send_data_if_last_ping_is_over_one_day_ago() throws IOException {
- initTelemetrySettingsToDefaultValues();
- when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
- settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
- long today = parseDate("2017-08-01").getTime();
- system2.setNow(today);
- long oneDayAgo = today - ONE_DAY - ONE_HOUR;
- internalProperties.write("telemetry.lastPing", String.valueOf(oneDayAgo));
- reset(internalProperties);
- when(dataLoader.load()).thenReturn(SOME_TELEMETRY_DATA);
- mockDataJsonWriterDoingSomething();
-
- underTest.start();
-
- verify(internalProperties, timeout(4_000)).write("telemetry.lastPing", String.valueOf(today));
- verify(client).upload(anyString());
- }
-
- @Test
- public void opt_out_sent_once() throws IOException {
- initTelemetrySettingsToDefaultValues();
- when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
- settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
- settings.setProperty("sonar.telemetry.enable", "false");
- mockDataJsonWriterDoingSomething();
-
- underTest.start();
- underTest.start();
-
- verify(client, after(2_000).never()).upload(anyString());
- verify(client, timeout(2_000).times(1)).optOut(anyString());
- assertThat(logger.logs(Level.INFO)).contains("Sharing of SonarQube statistics is disabled.");
- }
-
- @Test
- public void write_sequence_as_one_if_not_previously_present() {
- initTelemetrySettingsToDefaultValues();
- when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
- settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
- mockDataJsonWriterDoingSomething();
-
- underTest.start();
-
- verify(internalProperties, timeout(4_000)).write("telemetry.messageSeq", "1");
- }
-
- @Test
- public void write_sequence_correctly_incremented() {
- initTelemetrySettingsToDefaultValues();
- when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
- settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
- internalProperties.write("telemetry.messageSeq", "10");
- mockDataJsonWriterDoingSomething();
-
- underTest.start();
-
- verify(internalProperties, timeout(4_000)).write("telemetry.messageSeq", "10");
-
- // force another ping
- internalProperties.write("telemetry.lastPing", String.valueOf(system2.now() - ONE_DAY));
-
- verify(internalProperties, timeout(4_000)).write("telemetry.messageSeq", "11");
- }
-
- private void initTelemetrySettingsToDefaultValues() {
- settings.setProperty(SONAR_TELEMETRY_ENABLE.getKey(), SONAR_TELEMETRY_ENABLE.getDefaultValue());
- settings.setProperty(SONAR_TELEMETRY_URL.getKey(), SONAR_TELEMETRY_URL.getDefaultValue());
- settings.setProperty(SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getKey(), SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getDefaultValue());
- }
-
-}
diff --git a/server/sonar-webserver-core/src/test/resources/org/sonar/server/telemetry/dummy.crt b/server/sonar-webserver-core/src/test/resources/org/sonar/server/telemetry/dummy.crt
deleted file mode 100644
index 10e8a4a760e..00000000000
--- a/server/sonar-webserver-core/src/test/resources/org/sonar/server/telemetry/dummy.crt
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICQjCCAaugAwIBAgIBADANBgkqhkiG9w0BAQ0FADA9MQswCQYDVQQGEwJ1czEO
-MAwGA1UECAwFZHVtbXkxDjAMBgNVBAoMBWR1bW15MQ4wDAYDVQQDDAVkdW1teTAg
-Fw0yMzA2MDkxMDMxMzRaGA8yMjk3MDMyNDEwMzEzNFowPTELMAkGA1UEBhMCdXMx
-DjAMBgNVBAgMBWR1bW15MQ4wDAYDVQQKDAVkdW1teTEOMAwGA1UEAwwFZHVtbXkw
-gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPL0Byqouz9UNBFRLqRRuNdGniwh
-LzheMFKsdQIasTddfbsne6IuqMIBRyNr/icPrxXZEx/LY7mlKpBCYM/yty5ngEon
-0QTTw/GXj3A7eDcpYD/0pVRKFcKNFIp58IKV09to2h4ttQUdjMqLS2yjc0ADugmy
-ctlTR90Yna31Gi/nAgMBAAGjUDBOMB0GA1UdDgQWBBSMaHVg1zegjAH8CEZdN87I
-FtN/6jAfBgNVHSMEGDAWgBSMaHVg1zegjAH8CEZdN87IFtN/6jAMBgNVHRMEBTAD
-AQH/MA0GCSqGSIb3DQEBDQUAA4GBAFRViPwyPMBY6auUmaywjeLqtVPfn58MNssN
-TZEh4ft3d2Z531m5thtSiZhnKFU/f1xRecUXK3jew8/RAKVSsTH7A4NYfhu5Bs/K
-JfFWv7NYwL5ntnaBQZQ5uSYwPwiTYZzFrTgEDDOkXpsf7g5A16hS/L11A1lwx9b4
-WWCrjmv4
------END CERTIFICATE-----
diff --git a/server/sonar-webserver-core/src/test/resources/org/sonar/server/telemetry/telemetry-example.json b/server/sonar-webserver-core/src/test/resources/org/sonar/server/telemetry/telemetry-example.json
deleted file mode 100644
index d9edc0ee818..00000000000
--- a/server/sonar-webserver-core/src/test/resources/org/sonar/server/telemetry/telemetry-example.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "id": "AU-TpxcB-iU5OvuD2FL7",
- "version": "7.5.4",
- "edition": "developer",
- "database": {
- "name": "PostgreSQL",
- "version": "9.6.5"
- },
- "plugins": [
- {
- "name": "java",
- "version": "4.12.0.11033"
- },
- {
- "name": "scmgit",
- "version": "1.2"
- },
- {
- "name": "other",
- "version": "undefined"
- }
- ],
- "userCount": 3,
- "projectCount": 2,
- "usingBranches": true,
- "ncloc": 300,
- "projectCountByLanguage": [
- {
- "language": "java",
- "count": 2
- },
- {
- "language": "kotlin",
- "count": 1
- },
- {
- "language": "js",
- "count": 1
- }
- ],
- "nclocByLanguage": [
- {
- "language": "java",
- "ncloc": 500
- },
- {
- "language": "kotlin",
- "ncloc": 2500
- },
- {
- "language": "js",
- "ncloc": 50
- }
- ],
- "docker": false
-}