Browse Source

SONAR-20359 add quality profile to projects in telemetry

tags/10.3.0.82913
Benjamin Campomenosi 9 months ago
parent
commit
e77ef17d27

+ 4
- 6
server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/QualityProfileDaoIT.java View File

@@ -817,11 +817,10 @@ public class QualityProfileDaoIT {
QProfileDto profile3 = newQualityProfileDto();

assertThat(underTest.selectAllProjectAssociations(dbSession))
.extracting("projectUuid", "projectKey", "projectName", "profileKey")
.extracting("projectUuid", "profileKey", "language")
.containsExactlyInAnyOrder(
tuple(project1.getUuid(), project1.getKey(), project1.getName(), profile1.getKee()),
tuple(project2.getUuid(), project2.getKey(), project2.getName(), profile2.getKee())
);
tuple(project1.getUuid(), profile1.getKee(), profile1.getLanguage()),
tuple(project2.getUuid(), profile2.getKee(), profile2.getLanguage()));
}

@Test
@@ -842,8 +841,7 @@ public class QualityProfileDaoIT {
.extracting("kee", "name", "language")
.containsExactlyInAnyOrder(
tuple(defaultProfile1.getKee(), defaultProfile1.getName(), defaultProfile1.getLanguage()),
tuple(defaultProfile2.getKee(), defaultProfile2.getName(), defaultProfile2.getLanguage())
);
tuple(defaultProfile2.getKee(), defaultProfile2.getName(), defaultProfile2.getLanguage()));
}

@Test

+ 23
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ProjectQProfileLanguageAssociationDto.java View File

@@ -0,0 +1,23 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.db.qualityprofile;

public record ProjectQProfileLanguageAssociationDto(String projectUuid, String profileKey, String language) {
}

+ 1
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java View File

@@ -248,7 +248,7 @@ public class QualityProfileDao implements Dao {
return mapper(dbSession).selectProjectAssociations(profile.getKee(), nameQuery);
}

public List<ProjectQprofileAssociationDto> selectAllProjectAssociations(DbSession dbSession) {
public List<ProjectQProfileLanguageAssociationDto> selectAllProjectAssociations(DbSession dbSession) {
return mapper(dbSession).selectAllProjectAssociations();
}
public Collection<String> selectUuidsOfCustomRulesProfiles(DbSession dbSession, String language, String name) {

+ 2
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java View File

@@ -109,6 +109,7 @@ public interface QualityProfileMapper {
@Param("oldProfileUuid") String oldProfileUuid);

void deleteProjectProfileAssociation(@Param("projectUuid") String projectUuid, @Param("profileUuid") String profileUuid);

void deleteProjectAssociationByProfileUuids(@Param("profileUuids") Collection<String> profileUuids);

List<ProjectQprofileAssociationDto> selectSelectedProjects(
@@ -123,7 +124,7 @@ public interface QualityProfileMapper {
@Param("profileUuid") String profileUuid,
@Param("nameOrKeyQuery") String nameOrKeyQuery);

List<ProjectQprofileAssociationDto> selectAllProjectAssociations();
List<ProjectQProfileLanguageAssociationDto> selectAllProjectAssociations();

List<String> selectUuidsOfCustomRuleProfiles(@Param("language") String language, @Param("name") String name);


+ 10
- 10
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml View File

@@ -387,16 +387,16 @@
ORDER BY pj.name ASC
</select>

<select id="selectAllProjectAssociations" resultType="org.sonar.db.qualityprofile.ProjectQprofileAssociationDto">
SELECT
pj.uuid as projectUuid,
pj.kee as projectKey,
pj.name as projectName,
pp.profile_key as profileKey
FROM projects pj
INNER JOIN project_qprofiles pp ON pp.project_uuid = pj.uuid
WHERE
pj.qualifier='TRK'
<select id="selectAllProjectAssociations" resultType="org.sonar.db.qualityprofile.ProjectQProfileLanguageAssociationDto">
SELECT
pj.uuid as projectUuid,
pp.profile_key as profileKey,
rp.language as language
FROM projects pj
INNER JOIN project_qprofiles pp ON pp.project_uuid = pj.uuid
INNER JOIN org_qprofiles oq ON pp.profile_key = oq.uuid
INNER JOIN rules_profiles rp on oq.rules_profile_uuid = rp.uuid
WHERE pj.qualifier = 'TRK'
</select>

<select id="selectUuidsOfCustomRuleProfiles" parameterType="map" resultType="string">

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

@@ -356,7 +356,7 @@ public class TelemetryData {
record Branch(String projectUuid, String branchUuid, int ncdId, int greenQualityGateCount, int analysisCount, boolean excludeFromPurge) {
}

record Project(String projectUuid, Long lastAnalysis, String language, Long loc) {
record Project(String projectUuid, Long lastAnalysis, String language, String qualityProfile, Long loc) {
}

record QualityGate(String uuid, String caycStatus) {

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

@@ -146,6 +146,7 @@ public class TelemetryDataJsonWriter {
}
json.prop(LANGUAGE_PROPERTY, project.language());
json.prop("loc", project.loc());
json.prop("qualityProfile", project.qualityProfile());
json.endObject();
});
json.endArray();

+ 4
- 1
server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java View File

@@ -400,18 +400,21 @@ public class TelemetryDataJsonWriterTest {
"projectUuid": "uuid-0",
"lastAnalysis": "1970-01-01T00:00:00+0000",
"language": "lang-0",
"qualityProfile" : "qprofile-0",
"loc": 2
},
{
"projectUuid": "uuid-1",
"lastAnalysis": "1970-01-01T00:00:00+0000",
"language": "lang-1",
"qualityProfile" : "qprofile-1",
"loc": 4
},
{
"projectUuid": "uuid-2",
"lastAnalysis": "1970-01-01T00:00:00+0000",
"language": "lang-2",
"qualityProfile" : "qprofile-2",
"loc": 6
}
]
@@ -656,7 +659,7 @@ public class TelemetryDataJsonWriterTest {
}

private static List<TelemetryData.Project> attachProjects() {
return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, (i + 1L) * 2L)).toList();
return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, "qprofile-" + i, (i + 1L) * 2L)).toList();
}

private static List<TelemetryData.ProjectStatistics> attachProjectStatsWithMetrics() {

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

@@ -116,6 +116,8 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
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
@@ -160,6 +162,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
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());
@@ -211,6 +214,8 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
this.ncdByBranch.clear();
this.ncdByProject.clear();
this.instanceNcd = NewCodeDefinition.getInstanceDefault();
this.defaultQualityProfileByLanguage.clear();
this.qualityProfileByProjectAndLanguage.clear();
}

private void loadNewCodeDefinitions(DbSession dbSession, List<BranchMeasuresDto> branchMeasuresDtos) {
@@ -245,6 +250,16 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
}
}

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;
@@ -274,13 +289,12 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
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, PrBranchAnalyzedLanguageCountByProjectDto> prAndBranchCountByProject =
dbClient.branchDao().countPrBranchAnalyzedLanguageByProjectUuid(dbSession)
.stream().collect(toMap(PrBranchAnalyzedLanguageCountByProjectDto::getProjectUuid, Function.identity()));
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, 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<>();
@@ -336,7 +350,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
data.setProjects(buildProjectsList(branchesWithLargestNcloc, latestSnapshotMap));
}

private static List<TelemetryData.Project> buildProjectsList(List<ProjectLocDistributionDto> branchesWithLargestNcloc,
private List<TelemetryData.Project> buildProjectsList(List<ProjectLocDistributionDto> branchesWithLargestNcloc,
Map<String, Long> latestSnapshotMap) {
return branchesWithLargestNcloc.stream()
.flatMap(measure -> Arrays.stream(measure.locDistribution().split(";"))
@@ -345,9 +359,17 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
measure.projectUuid(),
latestSnapshotMap.get(measure.branchUuid()),
languageAndLoc[0],
Long.parseLong(languageAndLoc[1])
))
).toList();
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) {
@@ -362,15 +384,12 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
for (QualityGateDto qualityGateDto : qualityGateDtos) {
qualityGates.add(
new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession,
qualityGateDto.getUuid()).toString())
);
qualityGateDto.getUuid()).toString()));
}

data.setQualityGates(qualityGates);
}



private void resolveUsers(TelemetryData.Builder data, DbSession dbSession) {
data.setUsers(dbClient.userDao().selectUsersForTelemetry(dbSession));
}
@@ -465,4 +484,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
private TelemetryData.CloudUsage buildCloudUsage() {
return cloudUsageDataProvider.getCloudUsage();
}

private record ProjectLanguageKey(String projectKey, String language) {
}
}

+ 58
- 1
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java View File

@@ -219,6 +219,12 @@ public class TelemetryDataLoaderImplTest {
QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate(qg -> qg.setName("QG2"));

//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);
@@ -296,7 +302,13 @@ public class TelemetryDataLoaderImplTest {

assertThat(data.getQualityProfiles())
.extracting(TelemetryData.QualityProfile::uuid, TelemetryData.QualityProfile::isBuiltIn)
.containsExactlyInAnyOrder(tuple(qualityProfile1.getKee(), qualityProfile1.isBuiltIn()), tuple(qualityProfile2.getKee(), qualityProfile2.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())
);

}

@@ -372,6 +384,12 @@ public class TelemetryDataLoaderImplTest {
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));
@@ -403,6 +421,45 @@ public class TelemetryDataLoaderImplTest {
tuple(2L, 0L, "undetected", "undetected"));
}

@Test
public void load_shouldProvideQualityProfileInProjectSection() {
server.setId("AU-TpxcB-iU5OvuD2FL7").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 test_ncd_on_community_edition() {
server.setId("AU-TpxcB-iU5OvuD2FL7").setVersion("7.5.4");

Loading…
Cancel
Save