@@ -0,0 +1,81 @@ | |||
/* | |||
* 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.server.qualitygate; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.Stream; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.metric.MetricDto; | |||
import org.sonar.db.qualitygate.QualityGateConditionDto; | |||
import static java.util.stream.Collectors.toUnmodifiableMap; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING; | |||
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; | |||
public class QualityGateCaycChecker { | |||
private static final Map<String, Double> CAYC_REQUIREMENTS = Stream.of( | |||
NEW_MAINTAINABILITY_RATING, | |||
NEW_RELIABILITY_RATING, | |||
NEW_SECURITY_HOTSPOTS_REVIEWED, | |||
NEW_SECURITY_RATING | |||
).collect(toUnmodifiableMap(Metric::getKey, Metric::getBestValue)); | |||
private final DbClient dbClient; | |||
public QualityGateCaycChecker(DbClient dbClient) { | |||
this.dbClient = dbClient; | |||
} | |||
public boolean checkCaycCompliant(DbSession dbSession, String qualityGateUuid) { | |||
var conditionsByMetricId = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateUuid) | |||
.stream() | |||
.collect(uniqueIndex(QualityGateConditionDto::getMetricUuid)); | |||
var metrics = dbClient.metricDao().selectByUuids(dbSession, conditionsByMetricId.keySet()) | |||
.stream() | |||
.filter(MetricDto::isEnabled) | |||
.collect(Collectors.toSet()); | |||
long count = metrics.stream() | |||
.filter(metric -> CAYC_REQUIREMENTS.containsKey(metric.getKey())) | |||
.filter(metric -> checkMetricCaycCompliant(conditionsByMetricId.get(metric.getUuid()), metric)) | |||
.count(); | |||
return count == CAYC_REQUIREMENTS.size(); | |||
} | |||
public boolean checkCaycCompliantFromProject(DbSession dbSession, String projectUuid) { | |||
return Optional.ofNullable(dbClient.qualityGateDao().selectByProjectUuid(dbSession, projectUuid)) | |||
.or(() -> Optional.ofNullable(dbClient.qualityGateDao().selectDefault(dbSession))) | |||
.map(qualityGate -> checkCaycCompliant(dbSession, qualityGate.getUuid())) | |||
.orElse(false); | |||
} | |||
private static boolean checkMetricCaycCompliant(QualityGateConditionDto condition, MetricDto metric) { | |||
Double errorThreshold = Double.valueOf(condition.getErrorThreshold()); | |||
Double caycRequiredThreshold = CAYC_REQUIREMENTS.get(metric.getKey()); | |||
return caycRequiredThreshold.compareTo(errorThreshold) == 0; | |||
} | |||
} |
@@ -26,6 +26,7 @@ public class QualityGateModule extends Module { | |||
protected void configureModule() { | |||
add( | |||
QualityGateUpdater.class, | |||
QualityGateCaycChecker.class, | |||
QualityGateConditionsUpdater.class, | |||
QualityGateFinder.class, | |||
QualityGateEvaluatorImpl.class); |
@@ -29,6 +29,7 @@ import org.sonar.api.server.ws.WebService; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.qualitygate.QualityGateDto; | |||
import org.sonar.server.qualitygate.QualityGateCaycChecker; | |||
import org.sonar.server.qualitygate.QualityGateFinder; | |||
import org.sonarqube.ws.Qualitygates.ListWsResponse; | |||
import org.sonarqube.ws.Qualitygates.ListWsResponse.QualityGate; | |||
@@ -42,11 +43,13 @@ public class ListAction implements QualityGatesWsAction { | |||
private final DbClient dbClient; | |||
private final QualityGatesWsSupport wsSupport; | |||
private final QualityGateFinder finder; | |||
private final QualityGateCaycChecker qualityGateCaycChecker; | |||
public ListAction(DbClient dbClient, QualityGatesWsSupport wsSupport, QualityGateFinder finder) { | |||
public ListAction(DbClient dbClient, QualityGatesWsSupport wsSupport, QualityGateFinder finder, QualityGateCaycChecker qualityGateCaycChecker) { | |||
this.dbClient = dbClient; | |||
this.wsSupport = wsSupport; | |||
this.finder = finder; | |||
this.qualityGateCaycChecker = qualityGateCaycChecker; | |||
} | |||
@Override | |||
@@ -56,6 +59,7 @@ public class ListAction implements QualityGatesWsAction { | |||
.setSince("4.3") | |||
.setResponseExample(Resources.getResource(this.getClass(), "list-example.json")) | |||
.setChangelog( | |||
new Change("9.9", "'isCaycCompliant' field is added on quality gate"), | |||
new Change("8.4", "Field 'id' in the response is deprecated. Format changes from integer to string."), | |||
new Change("7.0", "'isDefault' field is added on quality gate"), | |||
new Change("7.0", "'default' field on root level is deprecated"), | |||
@@ -69,6 +73,7 @@ public class ListAction implements QualityGatesWsAction { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
QualityGateDto defaultQualityGate = finder.getDefault(dbSession); | |||
Collection<QualityGateDto> qualityGates = dbClient.qualityGateDao().selectAll(dbSession); | |||
writeProtobuf(buildResponse(dbSession, qualityGates, defaultQualityGate), request, response); | |||
} | |||
} | |||
@@ -83,6 +88,7 @@ public class ListAction implements QualityGatesWsAction { | |||
.setName(qualityGate.getName()) | |||
.setIsDefault(qualityGate.getUuid().equals(defaultUuid)) | |||
.setIsBuiltIn(qualityGate.isBuiltIn()) | |||
.setIsCaycCompliant(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid())) | |||
.setActions(wsSupport.getActions(dbSession, qualityGate, defaultQualityGate)) | |||
.build()) | |||
.collect(toList())); |
@@ -41,6 +41,7 @@ import org.sonar.db.permission.GlobalPermission; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.qualitygate.QualityGateCaycChecker; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonar.server.ws.KeyExamples; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | |||
@@ -67,11 +68,13 @@ public class ProjectStatusAction implements QualityGatesWsAction { | |||
private final DbClient dbClient; | |||
private final ComponentFinder componentFinder; | |||
private final UserSession userSession; | |||
private final QualityGateCaycChecker qualityGateCaycChecker; | |||
public ProjectStatusAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { | |||
public ProjectStatusAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, QualityGateCaycChecker qualityGateCaycChecker) { | |||
this.dbClient = dbClient; | |||
this.componentFinder = componentFinder; | |||
this.userSession = userSession; | |||
this.qualityGateCaycChecker = qualityGateCaycChecker; | |||
} | |||
@Override | |||
@@ -87,11 +90,12 @@ public class ProjectStatusAction implements QualityGatesWsAction { | |||
"<li>'Administer' rights on the specified project</li>" + | |||
"<li>'Browse' on the specified project</li>" + | |||
"<li>'Execute Analysis' on the specified project</li>" + | |||
"</ul>",MSG_ONE_PROJECT_PARAMETER_ONLY, QG_STATUSES_ONE_LINE, ProjectStatusResponse.Status.NONE)) | |||
"</ul>", MSG_ONE_PROJECT_PARAMETER_ONLY, QG_STATUSES_ONE_LINE, ProjectStatusResponse.Status.NONE)) | |||
.setResponseExample(getClass().getResource("project_status-example.json")) | |||
.setSince("5.3") | |||
.setHandler(this) | |||
.setChangelog( | |||
new Change("9.9", "'isCaycCompliant' field is added to the response"), | |||
new Change("9.5", "The 'Execute Analysis' permission also allows to access the endpoint"), | |||
new Change("8.5", "The field 'periods' in the response is deprecated. Use 'period' instead"), | |||
new Change("7.7", "The parameters 'branch' and 'pullRequest' were added"), | |||
@@ -148,9 +152,10 @@ public class ProjectStatusAction implements QualityGatesWsAction { | |||
ProjectAndSnapshot projectAndSnapshot = getProjectAndSnapshot(dbSession, analysisId, projectUuid, projectKey, branchKey, pullRequestId); | |||
checkPermission(projectAndSnapshot.project); | |||
Optional<String> measureData = loadQualityGateDetails(dbSession, projectAndSnapshot, analysisId != null); | |||
var isCaycCompliant = qualityGateCaycChecker.checkCaycCompliantFromProject(dbSession, projectAndSnapshot.project.getUuid()); | |||
return ProjectStatusResponse.newBuilder() | |||
.setProjectStatus(new QualityGateDetailsFormatter(measureData, projectAndSnapshot.snapshotDto).format()) | |||
.setProjectStatus(new QualityGateDetailsFormatter(measureData.orElse(null), projectAndSnapshot.snapshotDto.orElse(null), isCaycCompliant).format()) | |||
.build(); | |||
} | |||
@@ -23,41 +23,28 @@ import com.google.gson.JsonArray; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.function.Predicate; | |||
import java.util.stream.Stream; | |||
import java.util.stream.StreamSupport; | |||
import javax.annotation.Nullable; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.db.component.SnapshotDto; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.NewCodePeriod; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Period; | |||
import static com.google.common.base.Strings.isNullOrEmpty; | |||
import static java.util.stream.Collectors.toUnmodifiableMap; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING; | |||
import static org.sonar.api.utils.DateUtils.formatDateTime; | |||
public class QualityGateDetailsFormatter { | |||
public static final String METRIC_KEY = "metric"; | |||
private static final Map<String, Double> CAYC_REQUIREMENTS = Stream.of( | |||
NEW_MAINTAINABILITY_RATING, | |||
NEW_RELIABILITY_RATING, | |||
NEW_SECURITY_HOTSPOTS_REVIEWED, | |||
NEW_SECURITY_RATING | |||
).collect(toUnmodifiableMap(Metric::getKey, Metric::getBestValue)); | |||
private final Optional<String> optionalMeasureData; | |||
private final Optional<SnapshotDto> optionalSnapshot; | |||
private final boolean isCaycCompliant; | |||
private final ProjectStatusResponse.ProjectStatus.Builder projectStatusBuilder; | |||
public QualityGateDetailsFormatter(Optional<String> measureData, Optional<SnapshotDto> snapshot) { | |||
this.optionalMeasureData = measureData; | |||
this.optionalSnapshot = snapshot; | |||
public QualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshot, boolean isCaycCompliant) { | |||
this.optionalMeasureData = Optional.ofNullable(measureData); | |||
this.optionalSnapshot = Optional.ofNullable(snapshot); | |||
this.isCaycCompliant = isCaycCompliant; | |||
this.projectStatusBuilder = ProjectStatusResponse.ProjectStatus.newBuilder(); | |||
} | |||
@@ -70,33 +57,15 @@ public class QualityGateDetailsFormatter { | |||
ProjectStatusResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString()); | |||
projectStatusBuilder.setStatus(qualityGateStatus); | |||
projectStatusBuilder.setIsCaycCompliant(isCaycCompliant); | |||
formatIgnoredConditions(json); | |||
formatConditions(json.getAsJsonArray("conditions")); | |||
formatCleanAsYouCodeCompliant(json.getAsJsonArray("conditions")); | |||
formatPeriods(); | |||
return projectStatusBuilder.build(); | |||
} | |||
private void formatCleanAsYouCodeCompliant(@Nullable JsonArray jsonConditions) { | |||
if (jsonConditions == null) { | |||
return; | |||
} | |||
long matchCount = jsonConditions.asList().stream() | |||
.map(JsonElement::getAsJsonObject) | |||
.filter(jsonObject -> CAYC_REQUIREMENTS.containsKey(jsonObject.get(METRIC_KEY).getAsString())) | |||
.filter(jsonObject -> { | |||
String metricKey = jsonObject.get(METRIC_KEY).getAsString(); | |||
Double value = jsonObject.get("error").getAsDouble(); | |||
return CAYC_REQUIREMENTS.get(metricKey).compareTo(value) == 0; | |||
}) | |||
.count(); | |||
projectStatusBuilder.setIsCaycCompliant(matchCount == CAYC_REQUIREMENTS.size()); | |||
} | |||
private void formatIgnoredConditions(JsonObject json) { | |||
JsonElement ignoredConditions = json.get("ignoredConditions"); | |||
if (ignoredConditions != null) { | |||
@@ -201,7 +170,7 @@ public class QualityGateDetailsFormatter { | |||
} | |||
private static void formatConditionMetric(ProjectStatusResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) { | |||
JsonElement metric = jsonCondition.get(METRIC_KEY); | |||
JsonElement metric = jsonCondition.get("metric"); | |||
if (metric != null && !isNullOrEmpty(metric.getAsString())) { | |||
conditionBuilder.setMetricKey(metric.getAsString()); | |||
} | |||
@@ -234,8 +203,8 @@ public class QualityGateDetailsFormatter { | |||
throw new IllegalStateException(String.format("Unknown quality gate comparator '%s'", measureOp)); | |||
} | |||
private static ProjectStatusResponse.ProjectStatus newResponseWithoutQualityGateDetails() { | |||
return ProjectStatusResponse.ProjectStatus.newBuilder().setStatus(ProjectStatusResponse.Status.NONE).build(); | |||
private ProjectStatusResponse.ProjectStatus newResponseWithoutQualityGateDetails() { | |||
return ProjectStatusResponse.ProjectStatus.newBuilder().setStatus(ProjectStatusResponse.Status.NONE).setIsCaycCompliant(isCaycCompliant).build(); | |||
} | |||
private static Predicate<JsonObject> isConditionOnValidPeriod() { |
@@ -34,6 +34,7 @@ import org.sonar.db.DbSession; | |||
import org.sonar.db.metric.MetricDto; | |||
import org.sonar.db.qualitygate.QualityGateConditionDto; | |||
import org.sonar.db.qualitygate.QualityGateDto; | |||
import org.sonar.server.qualitygate.QualityGateCaycChecker; | |||
import org.sonar.server.qualitygate.QualityGateFinder; | |||
import org.sonarqube.ws.Qualitygates.ShowWsResponse; | |||
@@ -53,20 +54,20 @@ public class ShowAction implements QualityGatesWsAction { | |||
private final DbClient dbClient; | |||
private final QualityGateFinder qualityGateFinder; | |||
private final QualityGatesWsSupport wsSupport; | |||
private final QualityGateCaycChecker qualityGateCaycChecker; | |||
public ShowAction(DbClient dbClient, QualityGateFinder qualityGateFinder, QualityGatesWsSupport wsSupport) { | |||
public ShowAction(DbClient dbClient, QualityGateFinder qualityGateFinder, QualityGatesWsSupport wsSupport, QualityGateCaycChecker qualityGateCaycChecker) { | |||
this.dbClient = dbClient; | |||
this.qualityGateFinder = qualityGateFinder; | |||
this.wsSupport = wsSupport; | |||
this.qualityGateCaycChecker = qualityGateCaycChecker; | |||
} | |||
@Override | |||
public void define(WebService.NewController controller) { | |||
WebService.NewAction action = controller.createAction("show") | |||
.setDescription("Display the details of a quality gate") | |||
.setSince("4.3") | |||
@Override public void define(WebService.NewController controller) { | |||
WebService.NewAction action = controller.createAction("show").setDescription("Display the details of a quality gate").setSince("4.3") | |||
.setResponseExample(Resources.getResource(this.getClass(), "show-example.json")) | |||
.setChangelog( | |||
new Change("9.9", "'isCaycCompliant' field is added to the response"), | |||
new Change("8.4", "Parameter 'id' is deprecated. Format changes from integer to string. Use 'name' instead."), | |||
new Change("8.4", "Field 'id' in the response is deprecated."), | |||
new Change("7.6", "'period' and 'warning' fields of conditions are removed from the response"), | |||
@@ -83,8 +84,7 @@ public class ShowAction implements QualityGatesWsAction { | |||
.setExampleValue("My Quality Gate"); | |||
} | |||
@Override | |||
public void handle(Request request, Response response) { | |||
@Override public void handle(Request request, Response response) { | |||
String id = request.param(PARAM_ID); | |||
String name = request.param(PARAM_NAME); | |||
checkOneOfIdOrNamePresent(id, name); | |||
@@ -94,7 +94,8 @@ public class ShowAction implements QualityGatesWsAction { | |||
Collection<QualityGateConditionDto> conditions = getConditions(dbSession, qualityGate); | |||
Map<String, MetricDto> metricsByUuid = getMetricsByUuid(dbSession, conditions); | |||
QualityGateDto defaultQualityGate = qualityGateFinder.getDefault(dbSession); | |||
writeProtobuf(buildResponse(dbSession, qualityGate, defaultQualityGate, conditions, metricsByUuid), request, response); | |||
boolean isCaycCompliant = qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid()); | |||
writeProtobuf(buildResponse(dbSession, qualityGate, defaultQualityGate, conditions, metricsByUuid, isCaycCompliant), request, response); | |||
} | |||
} | |||
@@ -114,17 +115,16 @@ public class ShowAction implements QualityGatesWsAction { | |||
private Map<String, MetricDto> getMetricsByUuid(DbSession dbSession, Collection<QualityGateConditionDto> conditions) { | |||
Set<String> metricUuids = conditions.stream().map(QualityGateConditionDto::getMetricUuid).collect(toSet()); | |||
return dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream() | |||
.filter(MetricDto::isEnabled) | |||
.collect(uniqueIndex(MetricDto::getUuid)); | |||
return dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream().filter(MetricDto::isEnabled).collect(uniqueIndex(MetricDto::getUuid)); | |||
} | |||
private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate, | |||
Collection<QualityGateConditionDto> conditions, Map<String, MetricDto> metricsByUuid) { | |||
private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate, Collection<QualityGateConditionDto> conditions, | |||
Map<String, MetricDto> metricsByUuid, boolean isCaycCompliant) { | |||
return ShowWsResponse.newBuilder() | |||
.setId(qualityGate.getUuid()) | |||
.setName(qualityGate.getName()) | |||
.setIsBuiltIn(qualityGate.isBuiltIn()) | |||
.setIsCaycCompliant(isCaycCompliant) | |||
.addAllConditions(conditions.stream() | |||
.map(toWsCondition(metricsByUuid)) | |||
.collect(toList())) |
@@ -2,6 +2,7 @@ | |||
"projectStatus": { | |||
"status": "ERROR", | |||
"ignoredConditions": false, | |||
"isCaycCompliant": false, | |||
"conditions": [ | |||
{ | |||
"status": "ERROR", |
@@ -0,0 +1,98 @@ | |||
/* | |||
* 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.server.qualitygate; | |||
import java.util.List; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.IntStream; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.util.Uuids; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.metric.MetricDto; | |||
import org.sonar.db.qualitygate.QualityGateConditionDto; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING; | |||
public class QualityGateCaycCheckerTest { | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
QualityGateCaycChecker underTest = new QualityGateCaycChecker(db.getDbClient()); | |||
@Test | |||
public void checkCaycCompliant() { | |||
String qualityGateUuid = "abcd"; | |||
List<Metric<Integer>> CAYC_REQUIREMENT_METRICS = List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_SECURITY_RATING); | |||
CAYC_REQUIREMENT_METRICS | |||
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | |||
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isTrue(); | |||
} | |||
@Test | |||
public void check_Cayc_NonCompliant_with_lesser_threshold_value() { | |||
var metrics = List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_SECURITY_RATING).stream() | |||
.map(this::insertMetric) | |||
.collect(Collectors.toList()); | |||
IntStream.range(0, metrics.size()).forEach(idx -> { | |||
String qualityGateUuid = "abcd" + idx; | |||
for (int i = 0; i < metrics.size(); i++) { | |||
var metric = metrics.get(i); | |||
insertCondition(metric, qualityGateUuid, idx == i ? metric.getWorstValue() : metric.getBestValue()); | |||
} | |||
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse(); | |||
}); | |||
} | |||
@Test | |||
public void check_Cayc_NonCompliant_with_missing_metric() { | |||
String qualityGateUuid = "abcd"; | |||
List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED) | |||
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | |||
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse(); | |||
} | |||
private void insertCondition(MetricDto metricDto, String qualityGateUuid, Double threshold) { | |||
QualityGateConditionDto newCondition = new QualityGateConditionDto().setQualityGateUuid(qualityGateUuid) | |||
.setUuid(Uuids.create()) | |||
.setMetricUuid(metricDto.getUuid()) | |||
.setOperator("LT") | |||
.setErrorThreshold(threshold.toString()); | |||
db.getDbClient().gateConditionDao().insert(newCondition, db.getSession()); | |||
db.commit(); | |||
} | |||
private MetricDto insertMetric(Metric metric) { | |||
return db.measures().insertMetric(m -> m | |||
.setKey(metric.key()) | |||
.setValueType(metric.getType().name()) | |||
.setHidden(false) | |||
.setBestValue(metric.getBestValue()) | |||
.setBestValue(metric.getWorstValue()) | |||
.setDirection(metric.getDirection())); | |||
} | |||
} |
@@ -29,6 +29,6 @@ public class QualityGateModuleTest { | |||
public void verify_count_of_added_components() { | |||
ListContainer container = new ListContainer(); | |||
new QualityGateModule().configure(container); | |||
assertThat(container.getAddedObjects()).hasSize(4); | |||
assertThat(container.getAddedObjects()).hasSize(5); | |||
} | |||
} |
@@ -23,10 +23,12 @@ import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.qualitygate.QualityGateDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.component.TestComponentFinder; | |||
import org.sonar.server.qualitygate.QualityGateCaycChecker; | |||
import org.sonar.server.qualitygate.QualityGateFinder; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.WsActionTester; | |||
@@ -35,6 +37,10 @@ import org.sonarqube.ws.Qualitygates.ListWsResponse.QualityGate; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.api.AssertionsForClassTypes.tuple; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.ArgumentMatchers.eq; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES; | |||
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; | |||
import static org.sonar.test.JsonAssert.assertJson; | |||
@@ -50,8 +56,10 @@ public class ListActionTest { | |||
private final DbClient dbClient = db.getDbClient(); | |||
private final QualityGateFinder qualityGateFinder = new QualityGateFinder(dbClient); | |||
private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class); | |||
private final WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(), | |||
new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder)); | |||
new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder, qualityGateCaycChecker)); | |||
@Test | |||
public void list_quality_gates() { | |||
@@ -85,6 +93,25 @@ public class ListActionTest { | |||
tuple(qualityGate2.getUuid(), false)); | |||
} | |||
@Test | |||
public void test_isCaycCompliant_flag() { | |||
QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate(); | |||
QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate(); | |||
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate1.getUuid()))).thenReturn(true); | |||
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate2.getUuid()))).thenReturn(false); | |||
db.qualityGates().setDefaultQualityGate(qualityGate1); | |||
ListWsResponse response = ws.newRequest() | |||
.executeProtobuf(ListWsResponse.class); | |||
assertThat(response.getQualitygatesList()) | |||
.extracting(QualityGate::getId, QualityGate::getIsCaycCompliant) | |||
.containsExactlyInAnyOrder( | |||
tuple(qualityGate1.getUuid(), true), | |||
tuple(qualityGate2.getUuid(), false)); | |||
} | |||
@Test | |||
public void test_deprecated_default_field() { | |||
QualityGateDto defaultQualityGate = db.qualityGates().insertQualityGate(); |
@@ -37,10 +37,12 @@ import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.SnapshotDto; | |||
import org.sonar.db.metric.MetricDto; | |||
import org.sonar.db.permission.GlobalPermission; | |||
import org.sonar.db.qualitygate.QualityGateDto; | |||
import org.sonar.server.component.TestComponentFinder; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.qualitygate.QualityGateCaycChecker; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | |||
@@ -51,6 +53,10 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.api.Assertions.tuple; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.ArgumentMatchers.eq; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.db.component.SnapshotTesting.newAnalysis; | |||
import static org.sonar.db.measure.MeasureTesting.newLiveMeasure; | |||
import static org.sonar.db.measure.MeasureTesting.newMeasureDto; | |||
@@ -72,8 +78,9 @@ public class ProjectStatusActionTest { | |||
private final DbClient dbClient = db.getDbClient(); | |||
private final DbSession dbSession = db.getSession(); | |||
private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class); | |||
private final WsActionTester ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession)); | |||
private final WsActionTester ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession, qualityGateCaycChecker)); | |||
@Test | |||
public void test_definition() { | |||
@@ -316,6 +323,23 @@ public class ProjectStatusActionTest { | |||
.executeProtobuf(ProjectStatusResponse.class); | |||
} | |||
@Test | |||
public void check_cayc_compliant_flag() { | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
var qg = db.qualityGates().insertBuiltInQualityGate(); | |||
db.qualityGates().setDefaultQualityGate(qg); | |||
when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(DbSession.class), eq(project.uuid()))).thenReturn(true); | |||
SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(project)); | |||
dbSession.commit(); | |||
userSession.addProjectPermission(UserRole.USER, project); | |||
ProjectStatusResponse result = ws.newRequest() | |||
.setParam(PARAM_ANALYSIS_ID, snapshot.getUuid()) | |||
.executeProtobuf(ProjectStatusResponse.class); | |||
assertThat(result.getProjectStatus().getIsCaycCompliant()).isTrue(); | |||
} | |||
@Test | |||
public void user_with_project_scan_permission_is_allowed_to_get_project_status() { | |||
ComponentDto project = db.components().insertPrivateProject(); |
@@ -21,11 +21,9 @@ package org.sonar.server.qualitygate.ws; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.lang.text.StrSubstitutor; | |||
import org.junit.Test; | |||
import org.sonar.db.component.SnapshotDto; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | |||
@@ -51,6 +49,7 @@ public class QualityGateDetailsFormatterTest { | |||
ProjectStatus result = underTest.format(); | |||
assertThat(result.getStatus()).isEqualTo(ProjectStatusResponse.Status.ERROR); | |||
assertThat(result.getIsCaycCompliant()).isFalse(); | |||
// check conditions | |||
assertThat(result.getConditionsCount()).isEqualTo(3); | |||
List<ProjectStatusResponse.Condition> conditions = result.getConditionsList(); | |||
@@ -145,31 +144,7 @@ public class QualityGateDetailsFormatterTest { | |||
.hasMessageContaining("Unknown quality gate comparator 'UNKNOWN'"); | |||
} | |||
@Test | |||
public void verify_cayc_quality_gate_checked() throws IOException { | |||
String measureDataRaw = IOUtils.toString(getClass().getResource("QualityGateDetailsFormatterTest/cayc_compliant_qg.json")); | |||
String measureDataCompliant = StrSubstitutor.replace(measureDataRaw, Map.of("nmr_error", "1.0")); | |||
underTest = newQualityGateDetailsFormatter(measureDataCompliant, null); | |||
ProjectStatus result = underTest.format(); | |||
assertThat(result.getIsCaycCompliant()).isTrue(); | |||
String measureDataNonCompliant = StrSubstitutor.replace(measureDataRaw, Map.of("nmr_error", "2.0")); | |||
underTest = newQualityGateDetailsFormatter(measureDataNonCompliant, null); | |||
result = underTest.format(); | |||
assertThat(result.getIsCaycCompliant()).isFalse(); | |||
} | |||
@Test | |||
public void verify_cayc_quality_gate_with_missing_metric() throws IOException { | |||
String measureData = IOUtils.toString(getClass().getResource("QualityGateDetailsFormatterTest/cayc_missing_metric.json")); | |||
underTest = newQualityGateDetailsFormatter(measureData, null); | |||
ProjectStatus result = underTest.format(); | |||
assertThat(result.getIsCaycCompliant()).isFalse(); | |||
} | |||
private static QualityGateDetailsFormatter newQualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshotDto) { | |||
return new QualityGateDetailsFormatter(Optional.ofNullable(measureData), Optional.ofNullable(snapshotDto)); | |||
return new QualityGateDetailsFormatter(measureData, snapshotDto, false); | |||
} | |||
} |
@@ -24,6 +24,7 @@ import org.junit.Test; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.server.ws.WebService.Param; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.metric.MetricDto; | |||
import org.sonar.db.qualitygate.QualityGateConditionDto; | |||
@@ -31,6 +32,7 @@ import org.sonar.db.qualitygate.QualityGateDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.component.TestComponentFinder; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.qualitygate.QualityGateCaycChecker; | |||
import org.sonar.server.qualitygate.QualityGateFinder; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.WsActionTester; | |||
@@ -41,6 +43,10 @@ import static java.lang.String.format; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.api.AssertionsForClassTypes.tuple; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.ArgumentMatchers.eq; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES; | |||
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; | |||
import static org.sonar.test.JsonAssert.assertJson; | |||
@@ -52,10 +58,11 @@ public class ShowActionTest { | |||
public UserSessionRule userSession = UserSessionRule.standalone(); | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class); | |||
private final WsActionTester ws = new WsActionTester( | |||
new ShowAction(db.getDbClient(), new QualityGateFinder(db.getDbClient()), | |||
new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db)))); | |||
new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db)), qualityGateCaycChecker)); | |||
@Test | |||
public void show() { | |||
@@ -93,6 +100,19 @@ public class ShowActionTest { | |||
assertThat(response.getIsBuiltIn()).isTrue(); | |||
} | |||
@Test | |||
public void show_isCaycCompliant() { | |||
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); | |||
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate.getUuid()))).thenReturn(true); | |||
db.qualityGates().setDefaultQualityGate(qualityGate); | |||
ShowWsResponse response = ws.newRequest() | |||
.setParam("name", qualityGate.getName()) | |||
.executeProtobuf(ShowWsResponse.class); | |||
assertThat(response.getIsCaycCompliant()).isTrue(); | |||
} | |||
@Test | |||
public void show_by_id() { | |||
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); |
@@ -1,41 +0,0 @@ | |||
{ | |||
"level": "ERROR", | |||
"conditions": [ | |||
{ | |||
"metric": "new_maintainability_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "${nmr_error}", | |||
"actual": "2", | |||
"level": "ERROR" | |||
}, | |||
{ | |||
"metric": "new_reliability_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "1.0", | |||
"actual": "1", | |||
"level": "OK" | |||
}, | |||
{ | |||
"metric": "new_security_hotspots_reviewed", | |||
"op": "GT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "100.0", | |||
"actual": "100.0", | |||
"level": "OK" | |||
}, | |||
{ | |||
"metric": "new_security_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "1.0", | |||
"actual": "2", | |||
"level": "ERROR" | |||
} | |||
] | |||
} |
@@ -1,32 +0,0 @@ | |||
{ | |||
"level": "ERROR", | |||
"conditions": [ | |||
{ | |||
"metric": "new_reliability_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "1.0", | |||
"actual": "1", | |||
"level": "OK" | |||
}, | |||
{ | |||
"metric": "new_security_hotspots_reviewed", | |||
"op": "GT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "100.0", | |||
"actual": "100.0", | |||
"level": "OK" | |||
}, | |||
{ | |||
"metric": "new_security_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "1.0", | |||
"actual": "2", | |||
"level": "ERROR" | |||
} | |||
] | |||
} |
@@ -131,6 +131,7 @@ message ShowWsResponse { | |||
repeated Condition conditions = 3; | |||
optional bool isBuiltIn = 4; | |||
optional Actions actions = 5; | |||
optional bool isCaycCompliant = 6; | |||
message Condition { | |||
optional string id = 1; | |||
@@ -167,6 +168,7 @@ message ListWsResponse { | |||
optional bool isDefault = 3; | |||
optional bool isBuiltIn = 4; | |||
optional Actions actions = 5; | |||
optional bool isCaycCompliant = 6; | |||
} | |||
message RootActions { |