this.system2 = system2; | this.system2 = system2; | ||||
} | } | ||||
public List<LiveMeasureDto> selectByComponentUuidsAndMetricUuids(DbSession dbSession, Collection<String> largeComponentUuids, Collection<String> metricUuis) { | |||||
if (largeComponentUuids.isEmpty() || metricUuis.isEmpty()) { | |||||
public List<LiveMeasureDto> selectByComponentUuidsAndMetricUuids(DbSession dbSession, Collection<String> largeComponentUuids, Collection<String> metricUuids) { | |||||
if (largeComponentUuids.isEmpty() || metricUuids.isEmpty()) { | |||||
return Collections.emptyList(); | return Collections.emptyList(); | ||||
} | } | ||||
return executeLargeInputs( | return executeLargeInputs( | ||||
largeComponentUuids, | largeComponentUuids, | ||||
componentUuids -> mapper(dbSession).selectByComponentUuidsAndMetricUuids(componentUuids, metricUuis)); | |||||
componentUuids -> mapper(dbSession).selectByComponentUuidsAndMetricUuids(componentUuids, metricUuids)); | |||||
} | |||||
public List<LiveMeasureDto> selectForProjectsByMetricUuids(DbSession dbSession, Collection<String> metricUuids) { | |||||
return mapper(dbSession).selectForProjectsByMetricUuids(metricUuids); | |||||
} | } | ||||
public void scrollSelectByComponentUuidAndMetricKeys(DbSession dbSession, String componentUuid, Collection<String> metricKeys, ResultHandler<LiveMeasureDto> handler) { | public void scrollSelectByComponentUuidAndMetricKeys(DbSession dbSession, String componentUuid, Collection<String> metricKeys, ResultHandler<LiveMeasureDto> handler) { |
@Param("componentUuids") Collection<String> componentUuids, | @Param("componentUuids") Collection<String> componentUuids, | ||||
@Param("metricUuids") Collection<String> metricUuids); | @Param("metricUuids") Collection<String> metricUuids); | ||||
List<LiveMeasureDto> selectForProjectsByMetricUuids( | |||||
@Param("metricUuids") Collection<String> metricUuids); | |||||
List<LiveMeasureDto> selectByComponentUuidsAndMetricKeys( | List<LiveMeasureDto> selectByComponentUuidsAndMetricKeys( | ||||
@Param("componentUuids") Collection<String> componentUuids, | @Param("componentUuids") Collection<String> componentUuids, | ||||
@Param("metricKeys") Collection<String> metricKeys); | @Param("metricKeys") Collection<String> metricKeys); |
</foreach> | </foreach> | ||||
</select> | </select> | ||||
<select id="selectForProjectsByMetricUuids" parameterType="map" resultType="org.sonar.db.measure.LiveMeasureDto"> | |||||
select <include refid="columns"/> from live_measures lm | |||||
where | |||||
lm.metric_uuid in <foreach item="metricUuid" collection="metricUuids" open="(" separator="," | |||||
close=")">#{metricUuid, jdbcType=VARCHAR}</foreach> | |||||
and component_uuid in (SELECT | |||||
p.uuid as uuid | |||||
FROM projects p) | |||||
</select> | |||||
<select id="selectByComponentUuidsAndMetricKeys" parameterType="map" resultType="org.sonar.db.measure.LiveMeasureDto"> | <select id="selectByComponentUuidsAndMetricKeys" parameterType="map" resultType="org.sonar.db.measure.LiveMeasureDto"> | ||||
select <include refid="columns"/> from live_measures lm | select <include refid="columns"/> from live_measures lm | ||||
inner join metrics m on m.uuid = lm.metric_uuid | inner join metrics m on m.uuid = lm.metric_uuid |
assertThat(selected).isEmpty(); | assertThat(selected).isEmpty(); | ||||
} | } | ||||
@Test | |||||
public void selectForProjectsByMetricUuids() { | |||||
MetricDto metric = db.measures().insertMetric(); | |||||
MetricDto metric2 = db.measures().insertMetric(); | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
ComponentDto project2 = db.components().insertPrivateProject(); | |||||
underTest.insert(db.getSession(), newLiveMeasure(project, metric).setValue(3.14).setData((String) null)); | |||||
underTest.insert(db.getSession(), newLiveMeasure(project, metric2).setValue(4.54).setData((String) null)); | |||||
underTest.insert(db.getSession(), newLiveMeasure(project2, metric).setValue(99.99).setData((String) null)); | |||||
List<LiveMeasureDto> selected = underTest.selectForProjectsByMetricUuids(db.getSession(), List.of(metric.getUuid(), metric2.getUuid())); | |||||
assertThat(selected) | |||||
.extracting(LiveMeasureDto::getComponentUuid, LiveMeasureDto::getProjectUuid, LiveMeasureDto::getMetricUuid, LiveMeasureDto::getValue, LiveMeasureDto::getDataAsString) | |||||
.containsExactlyInAnyOrder( | |||||
tuple(project.uuid(), project.uuid(), metric.getUuid(), 3.14, null), | |||||
tuple(project.uuid(), project.uuid(), metric2.getUuid(), 4.54, null), | |||||
tuple(project2.uuid(), project2.uuid(), metric.getUuid(), 99.99, null)); | |||||
} | |||||
@Test | |||||
public void selectForProjectsByMetricUuids_whenMetricDoesNotMatch_shouldReturnEmptyList() { | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
underTest.insert(db.getSession(), newLiveMeasure(project, metric).setValue(3.14).setData((String) null)); | |||||
List<LiveMeasureDto> selected = underTest.selectForProjectsByMetricUuids(db.getSession(), singletonList("_other_")); | |||||
assertThat(selected).isEmpty(); | |||||
} | |||||
@Test | @Test | ||||
public void selectByComponentUuidAndMetricKey() { | public void selectByComponentUuidAndMetricKey() { | ||||
LiveMeasureDto measure = newLiveMeasure().setMetricUuid(metric.getUuid()); | LiveMeasureDto measure = newLiveMeasure().setMetricUuid(metric.getUuid()); |
record Project(String projectUuid, Long lastAnalysis, String language, Long loc) { | record Project(String projectUuid, Long lastAnalysis, String language, Long loc) { | ||||
} | } | ||||
record ProjectStatistics(String projectUuid, Long branchCount, Long pullRequestCount, String qualityGate, String scm, String ci, String devopsPlatform) { | |||||
record QualityGate(String uuid, boolean isCaycCompliant) { | |||||
} | } | ||||
record QualityGate(String uuid, boolean isCaycCompliant) { | |||||
public static class ProjectStatistics { | |||||
private final String projectUuid; | |||||
private final Long branchCount; | |||||
private final Long pullRequestCount; | |||||
private final String qualityGate; | |||||
private final String scm; | |||||
private final String ci; | |||||
private final String devopsPlatform; | |||||
private final Long bugs; | |||||
private final Long vulnerabilities; | |||||
private final Long securityHotspots; | |||||
private final Double technicalDebt; | |||||
private final Double developmentCost; | |||||
ProjectStatistics(Builder builder) { | |||||
this.projectUuid = builder.projectUuid; | |||||
this.branchCount = builder.branchCount; | |||||
this.pullRequestCount = builder.pullRequestCount; | |||||
this.qualityGate = builder.qualityGate; | |||||
this.scm = builder.scm; | |||||
this.ci = builder.ci; | |||||
this.devopsPlatform = builder.devopsPlatform; | |||||
this.bugs = builder.bugs; | |||||
this.vulnerabilities = builder.vulnerabilities; | |||||
this.securityHotspots = builder.securityHotspots; | |||||
this.technicalDebt = builder.technicalDebt; | |||||
this.developmentCost = builder.developmentCost; | |||||
} | |||||
public String getProjectUuid() { | |||||
return projectUuid; | |||||
} | |||||
public Long getBranchCount() { | |||||
return branchCount; | |||||
} | |||||
public Long getPullRequestCount() { | |||||
return pullRequestCount; | |||||
} | |||||
public String getQualityGate() { | |||||
return qualityGate; | |||||
} | |||||
public String getScm() { | |||||
return scm; | |||||
} | |||||
public String getCi() { | |||||
return ci; | |||||
} | |||||
public String getDevopsPlatform() { | |||||
return devopsPlatform; | |||||
} | |||||
public Optional<Long> getBugs() { | |||||
return Optional.ofNullable(bugs); | |||||
} | |||||
public Optional<Long> getVulnerabilities() { | |||||
return Optional.ofNullable(vulnerabilities); | |||||
} | |||||
public Optional<Long> getSecurityHotspots() { | |||||
return Optional.ofNullable(securityHotspots); | |||||
} | |||||
public Optional<Double> getTechnicalDebt() { | |||||
return Optional.ofNullable(technicalDebt); | |||||
} | |||||
public Optional<Double> getDevelopmentCost() { | |||||
return Optional.ofNullable(developmentCost); | |||||
} | |||||
static class Builder { | |||||
private String projectUuid; | |||||
private Long branchCount; | |||||
private Long pullRequestCount; | |||||
private String qualityGate; | |||||
private String scm; | |||||
private String ci; | |||||
private String devopsPlatform; | |||||
private Long bugs; | |||||
private Long vulnerabilities; | |||||
private Long securityHotspots; | |||||
private Double technicalDebt; | |||||
private Double developmentCost; | |||||
public Builder setProjectUuid(String projectUuid) { | |||||
this.projectUuid = projectUuid; | |||||
return this; | |||||
} | |||||
public Builder setBranchCount(Long branchCount) { | |||||
this.branchCount = branchCount; | |||||
return this; | |||||
} | |||||
public Builder setPRCount(Long pullRequestCount) { | |||||
this.pullRequestCount = pullRequestCount; | |||||
return this; | |||||
} | |||||
public Builder setQG(String qualityGate) { | |||||
this.qualityGate = qualityGate; | |||||
return this; | |||||
} | |||||
public Builder setScm(String scm) { | |||||
this.scm = scm; | |||||
return this; | |||||
} | |||||
public Builder setCi(String ci) { | |||||
this.ci = ci; | |||||
return this; | |||||
} | |||||
public Builder setDevops(String devopsPlatform) { | |||||
this.devopsPlatform = devopsPlatform; | |||||
return this; | |||||
} | |||||
public Builder setBugs(@Nullable Number bugs) { | |||||
this.bugs = bugs != null ? bugs.longValue() : null; | |||||
return this; | |||||
} | |||||
public Builder setVulnerabilities(@Nullable Number vulnerabilities) { | |||||
this.vulnerabilities = vulnerabilities != null ? vulnerabilities.longValue() : null; | |||||
return this; | |||||
} | |||||
public Builder setSecurityHotspots(@Nullable Number securityHotspots) { | |||||
this.securityHotspots = securityHotspots != null ? securityHotspots.longValue() : null; | |||||
return this; | |||||
} | |||||
public Builder setTechnicalDebt(@Nullable Number technicalDebt) { | |||||
this.technicalDebt = technicalDebt != null ? technicalDebt.doubleValue() : null; | |||||
return this; | |||||
} | |||||
public Builder setDevelopmentCost(@Nullable Number developmentCost) { | |||||
this.developmentCost = developmentCost != null ? developmentCost.doubleValue() : null; | |||||
return this; | |||||
} | |||||
public ProjectStatistics build() { | |||||
return new ProjectStatistics(this); | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
json.beginArray(); | json.beginArray(); | ||||
statistics.getProjectStatistics().forEach(project -> { | statistics.getProjectStatistics().forEach(project -> { | ||||
json.beginObject(); | json.beginObject(); | ||||
json.prop("projectUuid", project.projectUuid()); | |||||
json.prop("branchCount", project.branchCount()); | |||||
json.prop("pullRequestCount", project.pullRequestCount()); | |||||
json.prop("qualityGate", project.qualityGate()); | |||||
json.prop("scm", project.scm()); | |||||
json.prop("ci", project.ci()); | |||||
json.prop("devopsPlatform", project.devopsPlatform()); | |||||
json.prop("projectUuid", project.getProjectUuid()); | |||||
json.prop("branchCount", project.getBranchCount()); | |||||
json.prop("pullRequestCount", project.getPullRequestCount()); | |||||
json.prop("qualityGate", project.getQualityGate()); | |||||
json.prop("scm", project.getScm()); | |||||
json.prop("ci", project.getCi()); | |||||
json.prop("devopsPlatform", project.getDevopsPlatform()); | |||||
project.getBugs().ifPresent(bugs -> json.prop("bugs", bugs)); | |||||
project.getVulnerabilities().ifPresent(vulnerabilities -> json.prop("vulnerabilities", vulnerabilities)); | |||||
project.getSecurityHotspots().ifPresent(securityHotspots -> json.prop("securityHotspots", securityHotspots)); | |||||
project.getTechnicalDebt().ifPresent(technicalDebt -> json.prop("technicalDebt", technicalDebt)); | |||||
project.getDevelopmentCost().ifPresent(developmentCost -> json.prop("developmentCost", developmentCost)); | |||||
json.endObject(); | json.endObject(); | ||||
}); | }); | ||||
json.endArray(); | json.endArray(); |
@Test | @Test | ||||
public void writes_all_projects_stats_with_analyzed_languages() { | public void writes_all_projects_stats_with_analyzed_languages() { | ||||
TelemetryData data = telemetryBuilder() | TelemetryData data = telemetryBuilder() | ||||
.setProjectStatistics(attachProjectStats()) | |||||
.setProjectStatistics(attachProjectStatsWithMetrics()) | |||||
.build(); | .build(); | ||||
String json = writeTelemetryData(data); | String json = writeTelemetryData(data); | ||||
"projectUuid": "uuid-0", | "projectUuid": "uuid-0", | ||||
"branchCount": 2, | "branchCount": 2, | ||||
"pullRequestCount": 2, | "pullRequestCount": 2, | ||||
"qualityGate": "qg-0" | |||||
"qualityGate": "qg-0", | |||||
"scm": "scm-0", | "scm": "scm-0", | ||||
"ci": "ci-0", | "ci": "ci-0", | ||||
"devopsPlatform": "devops-0" | |||||
"devopsPlatform": "devops-0", | |||||
"bugs": 2, | |||||
"vulnerabilities": 3, | |||||
"securityHotspots": 4, | |||||
"technicalDebt": 60.0, | |||||
"developmentCost": 30.0 | |||||
}, | }, | ||||
{ | { | ||||
"projectUuid": "uuid-1", | "projectUuid": "uuid-1", | ||||
"branchCount": 4, | "branchCount": 4, | ||||
"pullRequestCount": 4, | "pullRequestCount": 4, | ||||
"qualityGate": "qg-1" | |||||
"qualityGate": "qg-1", | |||||
"scm": "scm-1", | "scm": "scm-1", | ||||
"ci": "ci-1", | "ci": "ci-1", | ||||
"devopsPlatform": "devops-1" | |||||
"devopsPlatform": "devops-1", | |||||
"bugs": 4, | |||||
"vulnerabilities": 6, | |||||
"securityHotspots": 8, | |||||
"technicalDebt": 120.0, | |||||
"developmentCost": 60.0 | |||||
}, | }, | ||||
{ | { | ||||
"projectUuid": "uuid-2", | "projectUuid": "uuid-2", | ||||
"branchCount": 6, | "branchCount": 6, | ||||
"pullRequestCount": 6, | "pullRequestCount": 6, | ||||
"qualityGate": "qg-2" | |||||
"qualityGate": "qg-2", | |||||
"scm": "scm-2", | "scm": "scm-2", | ||||
"ci": "ci-2", | "ci": "ci-2", | ||||
"devopsPlatform": "devops-2" | |||||
"devopsPlatform": "devops-2", | |||||
"bugs": 6, | |||||
"vulnerabilities": 9, | |||||
"securityHotspots": 12, | |||||
"technicalDebt": 180.0, | |||||
"developmentCost": 90.0 | |||||
} | } | ||||
] | ] | ||||
} | } | ||||
assertThat(json).doesNotContain("hasUnanalyzedC", "hasUnanalyzedCpp"); | assertThat(json).doesNotContain("hasUnanalyzedC", "hasUnanalyzedCpp"); | ||||
} | } | ||||
@Test | |||||
public void writes_all_projects_stats_without_missing_metrics() { | |||||
TelemetryData data = telemetryBuilder() | |||||
.setProjectStatistics(attachProjectStats()) | |||||
.build(); | |||||
String json = writeTelemetryData(data); | |||||
assertThat(json).doesNotContain("bugs", "vulnerabilities", "securityHotspots", "technicalDebt", "developmentCost"); | |||||
} | |||||
@Test | @Test | ||||
public void writes_all_quality_gates() { | public void writes_all_quality_gates() { | ||||
TelemetryData data = telemetryBuilder() | TelemetryData data = telemetryBuilder() | ||||
return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, (i + 1L) * 2L)).collect(Collectors.toList()); | return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, (i + 1L) * 2L)).collect(Collectors.toList()); | ||||
} | } | ||||
private List<TelemetryData.ProjectStatistics> attachProjectStats() { | |||||
return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.ProjectStatistics("uuid-" + i, (i + 1L) * 2L, (i + 1L) * 2L, "qg-" + i, "scm-" + i, "ci-" + i, "devops-" + i)) | |||||
.collect(Collectors.toList()); | |||||
private static List<TelemetryData.ProjectStatistics> attachProjectStatsWithMetrics() { | |||||
return IntStream.range(0, 3).mapToObj(i -> getProjectStatisticsWithMetricBuilder(i).build()).toList(); | |||||
} | |||||
private static List<TelemetryData.ProjectStatistics> attachProjectStats() { | |||||
return IntStream.range(0, 3).mapToObj(i -> getProjectStatisticsBuilder(i).build()).toList(); | |||||
} | |||||
private static TelemetryData.ProjectStatistics.Builder getProjectStatisticsBuilder(int i) { | |||||
return new TelemetryData.ProjectStatistics.Builder() | |||||
.setProjectUuid("uuid-" + i) | |||||
.setBranchCount((i + 1L) * 2L) | |||||
.setPRCount((i + 1L) * 2L) | |||||
.setQG("qg-" + i).setCi("ci-" + i) | |||||
.setScm("scm-" + i) | |||||
.setDevops("devops-" + i); | |||||
} | |||||
private static TelemetryData.ProjectStatistics.Builder getProjectStatisticsWithMetricBuilder(int i) { | |||||
return getProjectStatisticsBuilder(i) | |||||
.setBugs((i + 1L) * 2) | |||||
.setVulnerabilities((i + 1L) * 3) | |||||
.setSecurityHotspots((i + 1L) * 4) | |||||
.setDevelopmentCost((i + 1L) * 30d) | |||||
.setTechnicalDebt((i + 1L) * 60d); | |||||
} | } | ||||
private List<TelemetryData.QualityGate> attachQualityGates() { | private List<TelemetryData.QualityGate> attachQualityGates() { | ||||
@DataProvider | @DataProvider | ||||
public static Object[][] allEditions() { | public static Object[][] allEditions() { | ||||
return Arrays.stream(EditionProvider.Edition.values()) | return Arrays.stream(EditionProvider.Edition.values()) | ||||
.map(t -> new Object[] {t}) | |||||
.map(t -> new Object[]{t}) | |||||
.toArray(Object[][]::new); | .toArray(Object[][]::new); | ||||
} | } | ||||
import java.sql.SQLException; | import java.sql.SQLException; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Collections; | |||||
import java.util.HashMap; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import org.sonar.db.alm.setting.ProjectAlmKeyAndProject; | import org.sonar.db.alm.setting.ProjectAlmKeyAndProject; | ||||
import org.sonar.db.component.AnalysisPropertyValuePerProject; | import org.sonar.db.component.AnalysisPropertyValuePerProject; | ||||
import org.sonar.db.component.PrBranchAnalyzedLanguageCountByProjectDto; | import org.sonar.db.component.PrBranchAnalyzedLanguageCountByProjectDto; | ||||
import org.sonar.db.measure.LiveMeasureDto; | |||||
import org.sonar.db.measure.ProjectMeasureDto; | import org.sonar.db.measure.ProjectMeasureDto; | ||||
import org.sonar.db.metric.MetricDto; | |||||
import org.sonar.db.qualitygate.ProjectQgateAssociationDto; | import org.sonar.db.qualitygate.ProjectQgateAssociationDto; | ||||
import org.sonar.db.qualitygate.QualityGateDto; | import org.sonar.db.qualitygate.QualityGateDto; | ||||
import org.sonar.server.platform.DockerSupport; | import org.sonar.server.platform.DockerSupport; | ||||
import static java.util.Arrays.asList; | import static java.util.Arrays.asList; | ||||
import static java.util.Optional.ofNullable; | import static java.util.Optional.ofNullable; | ||||
import static java.util.stream.Collectors.groupingBy; | |||||
import static java.util.stream.Collectors.toMap; | |||||
import static org.sonar.api.internal.apachecommons.lang.StringUtils.startsWithIgnoreCase; | import static org.sonar.api.internal.apachecommons.lang.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_LANGUAGE_DISTRIBUTION_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_DETECTEDCI; | ||||
import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDSCM; | 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.COMMUNITY; | ||||
data.setVersion(server.getVersion()); | data.setVersion(server.getVersion()); | ||||
data.setEdition(editionProvider.get().orElse(null)); | data.setEdition(editionProvider.get().orElse(null)); | ||||
Function<PluginInfo, String> getVersion = plugin -> plugin.getVersion() == null ? "undefined" : plugin.getVersion().getName(); | Function<PluginInfo, String> getVersion = plugin -> plugin.getVersion() == null ? "undefined" : plugin.getVersion().getName(); | ||||
Map<String, String> plugins = pluginRepository.getPluginInfos().stream().collect(MoreCollectors.uniqueIndex(PluginInfo::getKey, getVersion)); | |||||
Map<String, String> plugins = pluginRepository.getPluginInfos().stream().collect(MoreCollectors.uniqueIndex(PluginInfo::getKey, | |||||
getVersion)); | |||||
data.setPlugins(plugins); | data.setPlugins(plugins); | ||||
try (DbSession dbSession = dbClient.openSession(false)) { | try (DbSession dbSession = dbClient.openSession(false)) { | ||||
data.setDatabase(loadDatabaseMetadata(dbSession)); | data.setDatabase(loadDatabaseMetadata(dbSession)); | ||||
Map<String, String> scmByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDSCM); | Map<String, String> scmByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDSCM); | ||||
Map<String, String> ciByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDCI); | Map<String, String> ciByProject = getAnalysisPropertyByProject(dbSession, SONAR_ANALYSIS_DETECTEDCI); | ||||
Map<String, ProjectAlmKeyAndProject> almAndUrlByProject = getAlmAndUrlByProject(dbSession); | Map<String, ProjectAlmKeyAndProject> almAndUrlByProject = getAlmAndUrlByProject(dbSession); | ||||
Map<String, PrBranchAnalyzedLanguageCountByProjectDto> prAndBranchCountByProjects = dbClient.branchDao().countPrBranchAnalyzedLanguageByProjectUuid(dbSession) | |||||
.stream().collect(Collectors.toMap(PrBranchAnalyzedLanguageCountByProjectDto::getProjectUuid, Function.identity())); | |||||
Map<String, String> projectQgatesMap = getProjectQgatesMap(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); | |||||
List<TelemetryData.ProjectStatistics> projectStatistics = new ArrayList<>(); | List<TelemetryData.ProjectStatistics> projectStatistics = new ArrayList<>(); | ||||
for (String projectUuid : projectUuids) { | for (String projectUuid : projectUuids) { | ||||
Optional<PrBranchAnalyzedLanguageCountByProjectDto> counts = ofNullable(prAndBranchCountByProjects.get(projectUuid)); | |||||
Long branchCount = counts.map(PrBranchAnalyzedLanguageCountByProjectDto::getBranch).orElse(0L); | |||||
Long pullRequestCount = counts.map(PrBranchAnalyzedLanguageCountByProjectDto::getPullRequest).orElse(0L); | |||||
String qualityGate = projectQgatesMap.getOrDefault(projectUuid, defaultQualityGateUuid); | |||||
String scm = Optional.ofNullable(scmByProject.get(projectUuid)).orElse(UNDETECTED); | |||||
String ci = Optional.ofNullable(ciByProject.get(projectUuid)).orElse(UNDETECTED); | |||||
String devopsPlatform = null; | |||||
if (almAndUrlByProject.containsKey(projectUuid)) { | |||||
ProjectAlmKeyAndProject projectAlmKeyAndProject = almAndUrlByProject.get(projectUuid); | |||||
devopsPlatform = getAlmName(projectAlmKeyAndProject.getAlmId(), projectAlmKeyAndProject.getUrl()); | |||||
} | |||||
devopsPlatform = Optional.ofNullable(devopsPlatform).orElse(UNDETECTED); | |||||
projectStatistics.add( | |||||
new TelemetryData.ProjectStatistics(projectUuid, branchCount, pullRequestCount, qualityGate, scm, ci, devopsPlatform)); | |||||
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(almAndUrlByProject, 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)) | |||||
.build(); | |||||
projectStatistics.add(stats); | |||||
} | } | ||||
data.setProjectStatistics(projectStatistics); | data.setProjectStatistics(projectStatistics); | ||||
} | } | ||||
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 void resolveProjects(TelemetryData.Builder data, DbSession dbSession) { | private void resolveProjects(TelemetryData.Builder data, DbSession dbSession) { | ||||
List<ProjectMeasureDto> measures = dbClient.measureDao().selectLastMeasureForAllProjects(dbSession, NCLOC_LANGUAGE_DISTRIBUTION_KEY); | List<ProjectMeasureDto> measures = dbClient.measureDao().selectLastMeasureForAllProjects(dbSession, NCLOC_LANGUAGE_DISTRIBUTION_KEY); | ||||
List<TelemetryData.Project> projects = new ArrayList<>(); | List<TelemetryData.Project> projects = new ArrayList<>(); | ||||
Collection<QualityGateDto> qualityGateDtos = dbClient.qualityGateDao().selectAll(dbSession); | Collection<QualityGateDto> qualityGateDtos = dbClient.qualityGateDao().selectAll(dbSession); | ||||
for (QualityGateDto qualityGateDto : qualityGateDtos) { | for (QualityGateDto qualityGateDto : qualityGateDtos) { | ||||
qualityGates.add( | qualityGates.add( | ||||
new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGateDto.getUuid())) | |||||
new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession, | |||||
qualityGateDto.getUuid())) | |||||
); | ); | ||||
} | } | ||||
return dbClient.analysisPropertiesDao() | return dbClient.analysisPropertiesDao() | ||||
.selectAnalysisPropertyValueInLastAnalysisPerProject(dbSession, analysisPropertyKey) | .selectAnalysisPropertyValueInLastAnalysisPerProject(dbSession, analysisPropertyKey) | ||||
.stream() | .stream() | ||||
.collect(Collectors.toMap(AnalysisPropertyValuePerProject::getProjectUuid, AnalysisPropertyValuePerProject::getPropertyValue)); | |||||
.collect(toMap(AnalysisPropertyValuePerProject::getProjectUuid, AnalysisPropertyValuePerProject::getPropertyValue)); | |||||
} | } | ||||
private Map<String, ProjectAlmKeyAndProject> getAlmAndUrlByProject(DbSession dbSession) { | private Map<String, ProjectAlmKeyAndProject> getAlmAndUrlByProject(DbSession dbSession) { | ||||
List<ProjectAlmKeyAndProject> projectAlmKeyAndProjects = dbClient.projectAlmSettingDao().selectAlmTypeAndUrlByProject(dbSession); | List<ProjectAlmKeyAndProject> projectAlmKeyAndProjects = dbClient.projectAlmSettingDao().selectAlmTypeAndUrlByProject(dbSession); | ||||
return projectAlmKeyAndProjects.stream().collect(Collectors.toMap(ProjectAlmKeyAndProject::getProjectUuid, Function.identity())); | |||||
return projectAlmKeyAndProjects.stream().collect(toMap(ProjectAlmKeyAndProject::getProjectUuid, Function.identity())); | |||||
} | } | ||||
private static String getAlmName(String alm, String url) { | private static String getAlmName(String alm, String url) { | ||||
private Map<String, String> getProjectQgatesMap(DbSession dbSession) { | private Map<String, String> getProjectQgatesMap(DbSession dbSession) { | ||||
return dbClient.projectQgateAssociationDao().selectAll(dbSession) | return dbClient.projectQgateAssociationDao().selectAll(dbSession) | ||||
.stream() | .stream() | ||||
.collect(Collectors.toMap(ProjectQgateAssociationDto::getUuid, p -> Optional.ofNullable(p.getGateUuid()).orElse(""))); | |||||
.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().selectForProjectsByMetricUuids(dbSession, metricNamesByUuid.keySet()) | |||||
.stream() | |||||
.collect(groupingBy(LiveMeasureDto::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) { | private static boolean checkIfCloudAlm(String almRaw, String alm, String url, String cloudUrl) { |
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
import static org.mockito.Mockito.spy; | import static org.mockito.Mockito.spy; | ||||
import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||
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.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.LINES_KEY; | ||||
import static org.sonar.api.measures.CoreMetrics.NCLOC_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.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_DETECTEDCI; | ||||
import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS_DETECTEDSCM; | 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.COMMUNITY; | ||||
internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder); | internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder); | ||||
private QualityGateDto builtInDefaultQualityGate; | private QualityGateDto builtInDefaultQualityGate; | ||||
private MetricDto bugsDto; | |||||
private MetricDto vulnerabilitiesDto; | |||||
private MetricDto securityHotspotsDto; | |||||
private MetricDto technicalDebtDto; | |||||
private MetricDto developmentCostDto; | |||||
@Before | @Before | ||||
public void setUpBuiltInQualityGate() { | public void setUpBuiltInQualityGate() { | ||||
String builtInQgName = "Sonar Way"; | String builtInQgName = "Sonar Way"; | ||||
builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true)); | builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true)); | ||||
db.qualityGates().setDefaultQualityGate(builtInDefaultQualityGate); | 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 | @Test | ||||
db.measures().insertLiveMeasure(project1, ncloc, m -> m.setValue(110d)); | db.measures().insertLiveMeasure(project1, ncloc, m -> m.setValue(110d)); | ||||
db.measures().insertLiveMeasure(project1, coverage, m -> m.setValue(80d)); | db.measures().insertLiveMeasure(project1, coverage, m -> m.setValue(80d)); | ||||
db.measures().insertLiveMeasure(project1, nclocDistrib, m -> m.setValue(null).setData("java=70;js=30;kotlin=10")); | db.measures().insertLiveMeasure(project1, nclocDistrib, m -> m.setValue(null).setData("java=70;js=30;kotlin=10")); | ||||
db.measures().insertLiveMeasure(project1, bugsDto, m -> m.setValue(1d)); | |||||
db.measures().insertLiveMeasure(project1, vulnerabilitiesDto, m -> m.setValue(1d).setData((String) null)); | |||||
db.measures().insertLiveMeasure(project1, securityHotspotsDto, m -> m.setValue(1d).setData((String) null)); | |||||
db.measures().insertLiveMeasure(project1, developmentCostDto, m -> m.setData("50").setValue(null)); | |||||
db.measures().insertLiveMeasure(project1, technicalDebtDto, m -> m.setValue(5d).setData((String) null)); | |||||
ComponentDto project2 = db.components().insertPrivateProject(); | ComponentDto project2 = db.components().insertPrivateProject(); | ||||
db.measures().insertLiveMeasure(project2, lines, m -> m.setValue(200d)); | db.measures().insertLiveMeasure(project2, lines, m -> m.setValue(200d)); | ||||
tuple(project2.uuid(), "java", 180L, analysisDate), | tuple(project2.uuid(), "java", 180L, analysisDate), | ||||
tuple(project2.uuid(), "js", 20L, analysisDate)); | tuple(project2.uuid(), "js", 20L, analysisDate)); | ||||
assertThat(data.getProjectStatistics()) | assertThat(data.getProjectStatistics()) | ||||
.extracting(TelemetryData.ProjectStatistics::branchCount, TelemetryData.ProjectStatistics::pullRequestCount, TelemetryData.ProjectStatistics::qualityGate, | |||||
TelemetryData.ProjectStatistics::scm, TelemetryData.ProjectStatistics::ci, TelemetryData.ProjectStatistics::devopsPlatform) | |||||
.extracting(TelemetryData.ProjectStatistics::getBranchCount, TelemetryData.ProjectStatistics::getPullRequestCount, TelemetryData.ProjectStatistics::getQualityGate, | |||||
TelemetryData.ProjectStatistics::getScm, TelemetryData.ProjectStatistics::getCi, TelemetryData.ProjectStatistics::getDevopsPlatform, | |||||
TelemetryData.ProjectStatistics::getBugs, TelemetryData.ProjectStatistics::getVulnerabilities, TelemetryData.ProjectStatistics::getSecurityHotspots, | |||||
TelemetryData.ProjectStatistics::getDevelopmentCost, TelemetryData.ProjectStatistics::getTechnicalDebt) | |||||
.containsExactlyInAnyOrder( | .containsExactlyInAnyOrder( | ||||
tuple(1L, 0L, qualityGate1.getUuid(), "scm-1", "ci-1", "azure_devops_cloud"), | |||||
tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud")); | |||||
tuple(1L, 0L, qualityGate1.getUuid(), "scm-1", "ci-1", "azure_devops_cloud", Optional.of(1L), Optional.of(1L), Optional.of(1L), Optional.of(50.0), Optional.of(5.0)), | |||||
tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud", Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); | |||||
assertThat(data.getQualityGates()) | assertThat(data.getQualityGates()) | ||||
.extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::isCaycCompliant) | .extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::isCaycCompliant) | ||||
.containsExactlyInAnyOrder( | .containsExactlyInAnyOrder( | ||||
tuple(project.uuid(), "js", 50L), | tuple(project.uuid(), "js", 50L), | ||||
tuple(project.uuid(), "kotlin", 30L)); | tuple(project.uuid(), "kotlin", 30L)); | ||||
assertThat(data.getProjectStatistics()) | assertThat(data.getProjectStatistics()) | ||||
.extracting(TelemetryData.ProjectStatistics::branchCount, TelemetryData.ProjectStatistics::pullRequestCount, | |||||
TelemetryData.ProjectStatistics::scm, TelemetryData.ProjectStatistics::ci) | |||||
.extracting(TelemetryData.ProjectStatistics::getBranchCount, TelemetryData.ProjectStatistics::getPullRequestCount, | |||||
TelemetryData.ProjectStatistics::getScm, TelemetryData.ProjectStatistics::getCi) | |||||
.containsExactlyInAnyOrder( | .containsExactlyInAnyOrder( | ||||
tuple(2L, 0L, "undetected", "undetected")); | tuple(2L, 0L, "undetected", "undetected")); | ||||
} | } | ||||
db.components().insertPublicProject(); | db.components().insertPublicProject(); | ||||
TelemetryData data = communityUnderTest.load(); | TelemetryData data = communityUnderTest.load(); | ||||
assertThat(data.getProjectStatistics()) | assertThat(data.getProjectStatistics()) | ||||
.extracting(TelemetryData.ProjectStatistics::devopsPlatform, TelemetryData.ProjectStatistics::scm, TelemetryData.ProjectStatistics::ci) | |||||
.extracting(TelemetryData.ProjectStatistics::getDevopsPlatform, TelemetryData.ProjectStatistics::getScm, TelemetryData.ProjectStatistics::getCi) | |||||
.containsExactlyInAnyOrder(tuple("undetected", "undetected", "undetected")); | .containsExactlyInAnyOrder(tuple("undetected", "undetected", "undetected")); | ||||
} | } | ||||
db.qualityGates().setDefaultQualityGate(qualityGate); | db.qualityGates().setDefaultQualityGate(qualityGate); | ||||
TelemetryData data = communityUnderTest.load(); | TelemetryData data = communityUnderTest.load(); | ||||
assertThat(data.getProjectStatistics()) | assertThat(data.getProjectStatistics()) | ||||
.extracting(TelemetryData.ProjectStatistics::qualityGate) | |||||
.extracting(TelemetryData.ProjectStatistics::getQualityGate) | |||||
.containsOnly(qualityGate.getUuid()); | .containsOnly(qualityGate.getUuid()); | ||||
} | } | ||||