Browse Source

SONAR-21818 Add information if project is part of a monorepo to telemetry.

tags/10.5.0.89998
Wojtek Wajerowicz 1 month ago
parent
commit
600f05786a

+ 25
- 9
server/sonar-db-dao/src/it/java/org/sonar/db/alm/setting/ProjectAlmSettingDaoIT.java View File

@@ -44,6 +44,7 @@ class ProjectAlmSettingDaoIT {
private static final long A_DATE_LATER = 1_700_000_000_000L;

private static final String A_UUID = "SOME_UUID";
private static final String ANOTHER_UUID = "SOME_UUID2";
private final TestSystem2 system2 = new TestSystem2().setNow(A_DATE);
@RegisterExtension
private final DbTester db = DbTester.create(system2);
@@ -117,8 +118,8 @@ class ProjectAlmSettingDaoIT {
AlmSettingDto gitlabSetting = db.almSettings().insertGitlabAlmSetting();
ProjectAlmSettingDto gitlabProject = createAlmProject(gitlabSetting);

List<ProjectAlmSettingDto> projectAlmSettingDtos =
underTest.selectByProjectUuidsAndAlm(dbSession, Set.of(githubProject.getProjectUuid(), gitlabProject.getProjectUuid()), ALM.GITHUB);
List<ProjectAlmSettingDto> projectAlmSettingDtos = underTest.selectByProjectUuidsAndAlm(dbSession, Set.of(githubProject.getProjectUuid(), gitlabProject.getProjectUuid()),
ALM.GITHUB);

assertThat(projectAlmSettingDtos)
.usingRecursiveFieldByFieldElementComparator()
@@ -171,15 +172,30 @@ class ProjectAlmSettingDaoIT {
}

@Test
void select_alm_type_and_url_by_project() {
when(uuidFactory.create()).thenReturn(A_UUID);
void selectAlmTypeAndUrlByProject_returnsCorrectValues() {
when(uuidFactory.create())
.thenReturn(A_UUID)
.thenReturn(ANOTHER_UUID);
AlmSettingDto almSettingsDto = db.almSettings().insertGitHubAlmSetting();
ProjectDto project = db.components().insertPrivateProject().getProjectDto();
ProjectAlmSettingDto githubProjectAlmSettingDto = newGithubProjectAlmSettingDto(almSettingsDto, project);
underTest.insertOrUpdate(dbSession, githubProjectAlmSettingDto, almSettingsDto.getKey(), project.getName(), project.getKey());

ProjectDto project1 = db.components().insertPrivateProject().getProjectDto();
ProjectDto project2 = db.components().insertPrivateProject().getProjectDto();

ProjectAlmSettingDto githubProjectAlmSettingDto1 = newGithubProjectAlmSettingDto(almSettingsDto, project1, false);
ProjectAlmSettingDto githubProjectAlmSettingDto2 = newGithubProjectAlmSettingDto(almSettingsDto, project2, true);

underTest.insertOrUpdate(dbSession, githubProjectAlmSettingDto1, almSettingsDto.getKey(), project1.getName(), project1.getKey());
underTest.insertOrUpdate(dbSession, githubProjectAlmSettingDto2, almSettingsDto.getKey(), project2.getName(), project2.getKey());

assertThat(underTest.selectAlmTypeAndUrlByProject(dbSession))
.extracting(ProjectAlmKeyAndProject::getProjectUuid, ProjectAlmKeyAndProject::getAlmId, ProjectAlmKeyAndProject::getUrl)
.containsExactly(tuple(project.getUuid(), almSettingsDto.getAlm().getId(), almSettingsDto.getUrl()));
.extracting(
ProjectAlmKeyAndProject::getProjectUuid,
ProjectAlmKeyAndProject::getAlmId,
ProjectAlmKeyAndProject::getUrl,
ProjectAlmKeyAndProject::getMonorepo
).containsExactlyInAnyOrder(
tuple(project1.getUuid(), almSettingsDto.getAlm().getId(), almSettingsDto.getUrl(), false),
tuple(project2.getUuid(), almSettingsDto.getAlm().getId(), almSettingsDto.getUrl(), true));
}

@Test

+ 9
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmKeyAndProject.java View File

@@ -24,6 +24,7 @@ public class ProjectAlmKeyAndProject {
private String projectUuid;
private String almId;
private String url;
private Boolean monorepo;

public ProjectAlmKeyAndProject() {
// keep empty
@@ -55,4 +56,12 @@ public class ProjectAlmKeyAndProject {
this.url = url;
return this;
}

public Boolean getMonorepo() {
return monorepo;
}
public ProjectAlmKeyAndProject setMonorepo(Boolean monorepo) {
this.monorepo = monorepo;
return this;
}
}

+ 2
- 1
server/sonar-db-dao/src/main/resources/org/sonar/db/alm/setting/ProjectAlmSettingMapper.xml View File

@@ -19,7 +19,8 @@
select
pas.project_uuid as "projectUuid",
alm_settings.alm_id as "almId",
alm_settings.url as "url"
alm_settings.url as "url",
pas.monorepo as "monorepo"
from
project_alm_settings pas
inner join

+ 5
- 1
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/almsettings/AlmSettingsDbTester.java View File

@@ -88,7 +88,11 @@ public class AlmSettingsDbTester {
}

public ProjectAlmSettingDto insertGitlabProjectAlmSetting(AlmSettingDto gitlabAlmSetting, ProjectDto project) {
return insertProjectAlmSetting(newGitlabProjectAlmSettingDto(gitlabAlmSetting, project), gitlabAlmSetting.getKey(),
return insertGitlabProjectAlmSetting(gitlabAlmSetting, project, false);
}

public ProjectAlmSettingDto insertGitlabProjectAlmSetting(AlmSettingDto gitlabAlmSetting, ProjectDto project, boolean monorepo) {
return insertProjectAlmSetting(newGitlabProjectAlmSettingDto(gitlabAlmSetting, project, monorepo), gitlabAlmSetting.getKey(),
project.getName(), project.getKey());
}


+ 10
- 2
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/almsettings/AlmSettingsTesting.java View File

@@ -78,19 +78,27 @@ public class AlmSettingsTesting {
}

public static ProjectAlmSettingDto newGithubProjectAlmSettingDto(AlmSettingDto githubAlmSetting, ProjectDto project) {
return newGithubProjectAlmSettingDto(githubAlmSetting, project, false);
}

public static ProjectAlmSettingDto newGithubProjectAlmSettingDto(AlmSettingDto githubAlmSetting, ProjectDto project, boolean monorepo) {
return new ProjectAlmSettingDto()
.setAlmSettingUuid(githubAlmSetting.getUuid())
.setProjectUuid(project.getUuid())
.setAlmRepo(randomAlphanumeric(256))
.setSummaryCommentEnabled(true)
.setMonorepo(false);
.setMonorepo(monorepo);
}

public static ProjectAlmSettingDto newGitlabProjectAlmSettingDto(AlmSettingDto gitlabAlmSetting, ProjectDto project) {
return newGitlabProjectAlmSettingDto(gitlabAlmSetting, project, false);
}

public static ProjectAlmSettingDto newGitlabProjectAlmSettingDto(AlmSettingDto gitlabAlmSetting, ProjectDto project, boolean monorepo) {
return new ProjectAlmSettingDto()
.setAlmSettingUuid(gitlabAlmSetting.getUuid())
.setProjectUuid(project.getUuid())
.setMonorepo(false);
.setMonorepo(monorepo);
}

static ProjectAlmSettingDto newAzureProjectAlmSettingDto(AlmSettingDto azureAlmSetting, ProjectDto project) {

+ 19
- 11
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java View File

@@ -27,7 +27,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.core.platform.EditionProvider;
import org.sonar.core.platform.EditionProvider.Edition;
import org.sonar.db.project.CreationMethod;
import org.sonar.db.user.UserTelemetryDto;
@@ -42,7 +41,7 @@ public class TelemetryData {
private final Long messageSequenceNumber;
private final Map<String, String> plugins;
private final Database database;
private final EditionProvider.Edition edition;
private final Edition edition;
private final String defaultQualityGate;
private final String sonarWayQualityGate;
private final Long installationDate;
@@ -109,7 +108,7 @@ public class TelemetryData {
return database;
}

public Optional<EditionProvider.Edition> getEdition() {
public Optional<Edition> getEdition() {
return Optional.ofNullable(edition);
}

@@ -325,7 +324,6 @@ public class TelemetryData {
return this;
}


Builder setQualityProfiles(List<QualityProfile> qualityProfiles) {
this.qualityProfiles = qualityProfiles;
return this;
@@ -378,18 +376,17 @@ public class TelemetryData {
}

public record QualityProfile(String uuid, @Nullable String parentUuid, String language, boolean isDefault,
boolean isBuiltIn,
@Nullable Boolean builtInParent, @Nullable Integer rulesOverriddenCount,
@Nullable Integer rulesActivatedCount, @Nullable Integer rulesDeactivatedCount
) {
boolean isBuiltIn,
@Nullable Boolean builtInParent, @Nullable Integer rulesOverriddenCount,
@Nullable Integer rulesActivatedCount, @Nullable Integer rulesDeactivatedCount) {
}

record ManagedInstanceInformation(boolean isManaged, @Nullable String provider) {
}

record CloudUsage(boolean kubernetes, @Nullable String kubernetesVersion, @Nullable String kubernetesPlatform,
@Nullable String kubernetesProvider,
@Nullable String officialHelmChart, @Nullable String containerRuntime, boolean officialImage) {
@Nullable String kubernetesProvider,
@Nullable String officialHelmChart, @Nullable String containerRuntime, boolean officialImage) {
}

public static class ProjectStatistics {
@@ -407,8 +404,8 @@ public class TelemetryData {
private final Long developmentCost;
private final int ncdId;
private final Long externalSecurityReportExportedAt;

private final CreationMethod creationMethod;
private final Boolean monorepo;

ProjectStatistics(Builder builder) {
this.projectUuid = builder.projectUuid;
@@ -426,6 +423,7 @@ public class TelemetryData {
this.ncdId = builder.ncdId;
this.externalSecurityReportExportedAt = builder.externalSecurityReportExportedAt;
this.creationMethod = builder.creationMethod;
this.monorepo = builder.monorepo;
}

public int getNcdId() {
@@ -488,6 +486,10 @@ public class TelemetryData {
return creationMethod;
}

public Boolean isMonorepo() {
return monorepo;
}

static class Builder {
private String projectUuid;
private Long branchCount;
@@ -504,6 +506,7 @@ public class TelemetryData {
private int ncdId;
private Long externalSecurityReportExportedAt;
private CreationMethod creationMethod;
private Boolean monorepo;

public Builder setProjectUuid(String projectUuid) {
this.projectUuid = projectUuid;
@@ -580,6 +583,11 @@ public class TelemetryData {
return this;
}

public Builder setMonorepo(Boolean monorepo) {
this.monorepo = monorepo;
return this;
}

public ProjectStatistics build() {
return new ProjectStatistics(this);
}

+ 1
- 0
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java View File

@@ -203,6 +203,7 @@ public class TelemetryDataJsonWriter {
json.prop("devopsPlatform", project.getDevopsPlatform());
json.prop(NCD_ID, project.getNcdId());
json.prop("project_creation_method", project.getCreationMethod().name());
json.prop("monorepo", project.isMonorepo());
project.getBugs().ifPresent(bugs -> json.prop("bugs", bugs));
project.getVulnerabilities().ifPresent(vulnerabilities -> json.prop("vulnerabilities", vulnerabilities));
project.getSecurityHotspots().ifPresent(securityHotspots -> json.prop("securityHotspots", securityHotspots));

+ 10
- 5
server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java View File

@@ -463,7 +463,8 @@ public class TelemetryDataJsonWriterTest {
"developmentCost": 30,
"ncdId": 12345,
"externalSecurityReportExportedAt": 1500000,
"project_creation_method": "LOCAL_API"
"project_creation_method": "LOCAL_API",
"monorepo": true
},
{
"projectUuid": "uuid-1",
@@ -480,7 +481,8 @@ public class TelemetryDataJsonWriterTest {
"developmentCost": 60,
"ncdId": 12345,
"externalSecurityReportExportedAt": 1500001,
"project_creation_method": "LOCAL_API"
"project_creation_method": "LOCAL_API",
"monorepo": false
},
{
"projectUuid": "uuid-2",
@@ -497,7 +499,8 @@ public class TelemetryDataJsonWriterTest {
"developmentCost": 90,
"ncdId": 12345,
"externalSecurityReportExportedAt": 1500002,
"project_creation_method": "LOCAL_API"
"project_creation_method": "LOCAL_API",
"monorepo": true
}
]
}
@@ -730,7 +733,8 @@ public class TelemetryDataJsonWriterTest {
.setScm("scm-" + i)
.setDevops("devops-" + i)
.setNcdId(NCD_ID)
.setCreationMethod(CreationMethod.LOCAL_API);
.setCreationMethod(CreationMethod.LOCAL_API)
.setMonorepo(false);
}

private static TelemetryData.ProjectStatistics.Builder getProjectStatisticsWithMetricBuilder(int i) {
@@ -741,7 +745,8 @@ public class TelemetryDataJsonWriterTest {
.setDevelopmentCost((i + 1L) * 30d)
.setTechnicalDebt((i + 1L) * 60d)
.setExternalSecurityReportExportedAt(1_500_000L + i)
.setCreationMethod(CreationMethod.LOCAL_API);
.setCreationMethod(CreationMethod.LOCAL_API)
.setMonorepo(i % 2 == 0);
}

private List<TelemetryData.QualityGate> attachQualityGates() {

+ 19
- 11
server/sonar-webserver-core/src/it/java/org/sonar/server/telemetry/TelemetryDataLoaderImplIT.java View File

@@ -215,7 +215,7 @@ public class TelemetryDataLoaderImplIT {
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());
db.almSettings().insertGitlabProjectAlmSetting(gitHubAlmSetting, projectData2.getProjectDto(), true);

// quality gates
QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG1").setBuiltIn(true));
@@ -238,8 +238,8 @@ public class TelemetryDataLoaderImplIT {
// link one project to a non-default QG
db.qualityGates().associateProjectToQualityGate(db.components().getProjectDtoByMainBranch(mainBranch1), qualityGate1);

var ncd1 = db.newCodePeriods().insert(projectData1.projectUuid(), NewCodePeriodType.NUMBER_OF_DAYS, "30");
var ncd2 = db.newCodePeriods().insert(projectData1.projectUuid(), branch2.branchUuid(), NewCodePeriodType.REFERENCE_BRANCH, "reference");
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();
@@ -274,15 +274,25 @@ public class TelemetryDataLoaderImplIT {
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)
.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),
projectNcdId, false),
tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud", Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
Optional.empty(), instanceNcdId));
Optional.empty(), instanceNcdId, true));

assertThat(data.getBranches())
.extracting(Branch::branchUuid, Branch::ncdId)
@@ -711,8 +721,6 @@ public class TelemetryDataLoaderImplIT {
.setCreatedAt(1L));
}



@DataProvider
public static Set<String> getScimFeatureStatues() {
HashSet<String> result = new HashSet<>();

+ 11
- 5
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java View File

@@ -222,7 +222,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
this.qualityProfileByProjectAndLanguage.clear();
}

private void loadNewCodeDefinitions(DbSession dbSession, List<BranchMeasuresDto> branchMeasuresDtos) {
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);
@@ -291,7 +291,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
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> almAndUrlByProject = getAlmAndUrlByProject(dbSession);
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);
@@ -313,7 +313,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
.setQG(qgatesByProject.getOrDefault(projectUuid, defaultQualityGateUuid))
.setScm(Optional.ofNullable(scmByProject.get(projectUuid)).orElse(UNDETECTED))
.setCi(Optional.ofNullable(ciByProject.get(projectUuid)).orElse(UNDETECTED))
.setDevops(resolveDevopsPlatform(almAndUrlByProject, projectUuid))
.setDevops(resolveDevopsPlatform(almAndUrlAndMonorepoByProject, projectUuid))
.setBugs(metrics.getOrDefault("bugs", null))
.setDevelopmentCost(metrics.getOrDefault("development_cost", null))
.setVulnerabilities(metrics.getOrDefault("vulnerabilities", null))
@@ -322,6 +322,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
.setNcdId(ncdByProject.getOrDefault(projectUuid, instanceNcd).hashCode())
.setExternalSecurityReportExportedAt(securityReportExportedAtByProjectUuid.get(projectUuid))
.setCreationMethod(project.getCreationMethod())
.setMonorepo(resolveMonorepo(almAndUrlAndMonorepoByProject, projectUuid))
.build();
projectStatistics.add(stats);
}
@@ -343,6 +344,12 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
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);
@@ -413,8 +420,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
Condition telemetryCondition = new Condition(
metricKey,
fromDbValue(condition.getOperator()),
condition.getErrorThreshold()
);
condition.getErrorThreshold());

conditionsMap
.computeIfAbsent(qualityGateUuid, k -> new ArrayList<>())

Loading…
Cancel
Save