@@ -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 |
@@ -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) { | |||
} |
@@ -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) { |
@@ -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); | |||
@@ -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"> |
@@ -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) { |
@@ -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(); |
@@ -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() { |
@@ -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) { | |||
} | |||
} |
@@ -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"); |