From 43e4f60243539bdbcfdcefea14774ee1e108fc1a Mon Sep 17 00:00:00 2001 From: Eric Hartmann Date: Wed, 22 Nov 2017 01:49:15 +0100 Subject: [PATCH] SONAR-10089 Automatically update built-in quality gate --- .../db/loadedtemplate/LoadedTemplateDto.java | 1 - .../qualitygate/QualityGateConditionDto.java | 2 +- .../sonar/db/qualitygate/QualityGateDao.java | 4 + .../db/qualitygate/QualityGateMapper.java | 2 + .../db/qualitygate/QualityGateMapper.xml | 9 + .../server/qualitygate/QualityGateFinder.java | 37 +-- .../qualitygate/QualityGateUpdater.java | 21 ++ .../server/qualitygate/QualityGates.java | 10 + .../qualitygate/RegisterQualityGates.java | 248 +++++++++++++--- .../qualitygate/QualityGateFinderTest.java | 7 +- .../qualitygate/RegisterQualityGatesTest.java | 279 +++++++++++++++--- .../OrganizationQualityGateUiTest.java | 4 +- .../tests/qualityGate/QualityGateUiTest.java | 2 +- .../sonarqube/tests/webhook/WebhooksTest.java | 2 +- ...project_overview_after_first_analysis.html | 2 +- 15 files changed, 529 insertions(+), 101 deletions(-) diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java index b8646499c6c..e099e96b426 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java @@ -23,7 +23,6 @@ import java.util.Objects; public final class LoadedTemplateDto { - public static final String QUALITY_GATE_TYPE = "QUALITY_GATE"; public static final String ONE_SHOT_TASK_TYPE = "ONE_SHOT_TASK"; private Long id; diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java index d3d0bbe5bac..7f108092ace 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java @@ -135,7 +135,7 @@ public class QualityGateConditionDto { return metricKey; } - public QualityGateConditionDto setMetricKey(String metricKey) { + public QualityGateConditionDto setMetricKey(@Nullable String metricKey) { this.metricKey = metricKey; return this; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java index 519e0dcb7bc..345538bfc8a 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java @@ -55,6 +55,10 @@ public class QualityGateDao implements Dao { mapper(session).update(qGate.setUpdatedAt(new Date())); } + public void ensureOneBuiltInQualityGate(DbSession dbSession, String builtInName) { + mapper(dbSession).ensureOneBuiltInQualityGate(builtInName); + } + private static QualityGateMapper mapper(DbSession session) { return session.getMapper(QualityGateMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java index 77548197d02..9347308d3c5 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java @@ -34,4 +34,6 @@ public interface QualityGateMapper { void delete(long id); void update(QualityGateDto qGate); + + void ensureOneBuiltInQualityGate(String builtInQualityName); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml index 290a4966886..332414e8b71 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml @@ -40,9 +40,18 @@ update quality_gates set name=#{name}, + is_built_in=#{isBuiltIn}, updated_at=#{updatedAt} where id=#{id} + + UPDATE quality_gates + SET + is_built_in=${_false} + WHERE + is_built_in=${_true} AND name <> #{builtInQualityName} + + diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java index ace49296fe0..b6b3ad0d8d1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java @@ -20,14 +20,13 @@ package org.sonar.server.qualitygate; import java.util.Optional; -import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.property.PropertyDto; import org.sonar.db.qualitygate.QualityGateDto; -import static org.apache.commons.lang.StringUtils.isBlank; import static org.sonar.server.qualitygate.QualityGates.SONAR_QUALITYGATE_PROPERTY; import static org.sonar.server.ws.WsUtils.checkFound; @@ -50,11 +49,11 @@ public class QualityGateFinder { if (qualityGateId.isPresent()) { return Optional.of(new QualityGateData(getById(dbSession, qualityGateId.get()), false)); } else { - QualityGateDto defaultQualityGate = getDefault(dbSession); - if (defaultQualityGate == null) { + Optional defaultQualityGate = getDefault(dbSession); + if (!defaultQualityGate.isPresent()) { return Optional.empty(); } - return Optional.of(new QualityGateData(defaultQualityGate, true)); + return Optional.of(new QualityGateData(defaultQualityGate.get(), true)); } } @@ -72,23 +71,27 @@ public class QualityGateFinder { throw new IllegalArgumentException("No parameter has been set to identify a quality gate"); } - @CheckForNull - private QualityGateDto getDefault(DbSession dbSession) { - Long defaultId = getDefaultId(dbSession); - if (defaultId == null) { - return null; + public Optional getDefault(DbSession dbSession) { + Optional defaultQualityGateId = getDefaultId(dbSession); + + if (!defaultQualityGateId.isPresent()) { + // For the moment, it's possible to have no default quality gate, but it will change with SONAR-8507 + return Optional.empty(); + } else { + return Optional.ofNullable( + dbClient.qualityGateDao().selectById(dbSession, defaultQualityGateId.get())); } - return getById(dbSession, defaultId); } - @CheckForNull - private Long getDefaultId(DbSession dbSession) { - PropertyDto defaultQgate = dbClient.propertiesDao().selectGlobalProperty(dbSession, SONAR_QUALITYGATE_PROPERTY); - if (defaultQgate == null || isBlank(defaultQgate.getValue())) { + private Optional getDefaultId(DbSession dbSession) { + PropertyDto defaultQualityGateId = dbClient.propertiesDao().selectGlobalProperty(dbSession, SONAR_QUALITYGATE_PROPERTY); + + if (defaultQualityGateId == null || StringUtils.isBlank(defaultQualityGateId.getValue())) { // For the moment, it's possible to have no default quality gate, but it will change with SONAR-8507 - return null; + return Optional.empty(); } - return Long.valueOf(defaultQgate.getValue()); + + return Optional.of(Long.valueOf(defaultQualityGateId.getValue())); } public static class QualityGateData { diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java index 722e593c586..4115a651a53 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java @@ -24,7 +24,9 @@ import java.util.List; import javax.annotation.Nullable; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.property.PropertyDto; import org.sonar.db.qualitygate.QualityGateDto; +import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.util.Validation; import static com.google.common.base.Strings.isNullOrEmpty; @@ -33,6 +35,8 @@ import static org.sonar.server.ws.WsUtils.checkRequest; public class QualityGateUpdater { + public static final String SONAR_QUALITYGATE_PROPERTY = "sonar.qualitygate"; + private final DbClient dbClient; public QualityGateUpdater(DbClient dbClient) { @@ -46,6 +50,23 @@ public class QualityGateUpdater { return newQualityGate; } + public void setDefault(DbSession dbSession, @Nullable QualityGateDto qualityGateDto) { + if (qualityGateDto == null) { + dbClient.propertiesDao().deleteGlobalProperty(SONAR_QUALITYGATE_PROPERTY, dbSession); + } else { + checkQualityGateExistence(dbSession, qualityGateDto.getId()); + dbClient.propertiesDao().saveProperty(dbSession, + new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY).setValue(qualityGateDto.getId().toString())); + } + } + + private void checkQualityGateExistence(DbSession dbSession, @Nullable Long qualityGateId) { + if (qualityGateId == null || + dbClient.qualityGateDao().selectById(dbSession, qualityGateId) == null) { + throw new NotFoundException("There is no quality gate with id=" + qualityGateId); + } + } + private void validateQualityGate(DbSession dbSession, @Nullable Long qGateId, @Nullable String name) { List errors = new ArrayList<>(); if (isNullOrEmpty(name)) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java index 941acb3ac30..2fc1da81afc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java @@ -100,6 +100,11 @@ public class QualityGates { } } + /** + * Use {@link QualityGateUpdater#setDefault(DbSession, QualityGateDto)} + * @deprecated + */ + @Deprecated public void setDefault(DbSession dbSession, @Nullable Long idToUseAsDefault) { checkIsQualityGateAdministrator(); if (idToUseAsDefault == null) { @@ -110,6 +115,11 @@ public class QualityGates { } } + /** + * Use {@link QualityGateUpdater#setDefault(DbSession, QualityGateDto)} + * @deprecated + */ + @Deprecated public void setDefault(@Nullable Long idToUseAsDefault) { try (DbSession dbSession = dbClient.openSession(false)) { setDefault(dbSession, idToUseAsDefault); diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java index 05394b65ed6..8e79e62d61e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java @@ -19,14 +19,29 @@ */ package org.sonar.server.qualitygate; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.picocontainer.Startable; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; -import org.sonar.db.loadedtemplate.LoadedTemplateDao; -import org.sonar.db.loadedtemplate.LoadedTemplateDto; +import org.sonar.db.metric.MetricDto; +import org.sonar.db.qualitygate.QualityGateConditionDao; +import org.sonar.db.qualitygate.QualityGateConditionDto; +import org.sonar.db.qualitygate.QualityGateDao; import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toMap; import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY; @@ -37,67 +52,222 @@ import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_LESS_THA public class RegisterQualityGates implements Startable { - private static final String BUILTIN_QUALITY_GATE = "SonarQube way"; + private static final Logger LOGGER = Loggers.get(RegisterQualityGates.class); + + private static final String BUILTIN_QUALITY_GATE_NAME = "Sonar way"; private static final int LEAK_PERIOD = 1; + private static final String A_RATING = Integer.toString(RatingGrid.Rating.A.getIndex()); + + private static final List QUALITY_GATE_CONDITIONS = asList( + new QualityGateCondition().setMetricKey(NEW_SECURITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setPeriod(LEAK_PERIOD).setErrorThreshold(A_RATING), + new QualityGateCondition().setMetricKey(NEW_RELIABILITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setPeriod(LEAK_PERIOD).setErrorThreshold(A_RATING), + new QualityGateCondition().setMetricKey(NEW_MAINTAINABILITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setPeriod(LEAK_PERIOD).setErrorThreshold(A_RATING), + new QualityGateCondition().setMetricKey(NEW_COVERAGE_KEY).setOperator(OPERATOR_LESS_THAN).setPeriod(LEAK_PERIOD).setErrorThreshold("80"), + new QualityGateCondition().setMetricKey(NEW_DUPLICATED_LINES_DENSITY_KEY).setOperator(OPERATOR_GREATER_THAN).setPeriod(LEAK_PERIOD).setErrorThreshold("3") + ); private final DbClient dbClient; private final QualityGateConditionsUpdater qualityGateConditionsUpdater; - private final LoadedTemplateDao loadedTemplateDao; - private final QualityGates qualityGates; + private final QualityGateFinder qualityGateFinder; + private final QualityGateUpdater qualityGateUpdater; + private final QualityGateDao qualityGateDao; + private final QualityGateConditionDao qualityGateConditionDao; + private final System2 system2; - public RegisterQualityGates(DbClient dbClient, QualityGateConditionsUpdater qualityGateConditionsUpdater, - LoadedTemplateDao loadedTemplateDao, QualityGates qualityGates) { + public RegisterQualityGates(DbClient dbClient, QualityGateUpdater qualityGateUpdater, + QualityGateConditionsUpdater qualityGateConditionsUpdater, QualityGateFinder qualityGateFinder, System2 system2) { this.dbClient = dbClient; this.qualityGateConditionsUpdater = qualityGateConditionsUpdater; - this.loadedTemplateDao = loadedTemplateDao; - this.qualityGates = qualityGates; + this.qualityGateUpdater = qualityGateUpdater; + this.qualityGateFinder = qualityGateFinder; + this.qualityGateDao = dbClient.qualityGateDao(); + this.qualityGateConditionDao = dbClient.gateConditionDao(); + this.system2 = system2; } @Override public void start() { try (DbSession dbSession = dbClient.openSession(false)) { - if (shouldRegisterBuiltinQualityGate(dbSession)) { - createBuiltinQualityGate(dbSession); - registerBuiltinQualityGate(dbSession); - dbSession.commit(); + QualityGateDto builtin = qualityGateDao.selectByName(dbSession, BUILTIN_QUALITY_GATE_NAME); + + // Create builtin if not present + if (builtin == null) { + LOGGER.info("Built-in quality gate [{}] has been created", BUILTIN_QUALITY_GATE_NAME); + builtin = createQualityGate(dbSession, BUILTIN_QUALITY_GATE_NAME); + } + + // Set builtin as default if there is no default + if (!qualityGateFinder.getDefault(dbSession).isPresent()) { + LOGGER.info("Built-in quality gate [{}] has been set as default", BUILTIN_QUALITY_GATE_NAME); + qualityGateUpdater.setDefault(dbSession, builtin); + } + + // Set builtin if missing + if (!builtin.isBuiltIn()) { + builtin.setBuiltIn(true); + dbClient.qualityGateDao().update(builtin, dbSession); + LOGGER.info("Quality gate [{}] has been set as built-in", BUILTIN_QUALITY_GATE_NAME); } + + updateQualityConditionsIfRequired(dbSession, builtin); + + qualityGateDao.ensureOneBuiltInQualityGate(dbSession, BUILTIN_QUALITY_GATE_NAME); + + dbSession.commit(); } } - @Override - public void stop() { - // do nothing - } + private void updateQualityConditionsIfRequired(DbSession dbSession, QualityGateDto builtin) { + Map idToKeyMetric = dbClient.metricDao().selectAll(dbSession).stream() + .collect(toMap(metricDto -> metricDto.getId().longValue(), MetricDto::getKey)); - private boolean shouldRegisterBuiltinQualityGate(DbSession dbSession) { - return loadedTemplateDao.countByTypeAndKey(LoadedTemplateDto.QUALITY_GATE_TYPE, BUILTIN_QUALITY_GATE, dbSession) == 0; + List qualityGateConditions = qualityGateConditionDao.selectForQualityGate(dbSession, builtin.getId()) + .stream() + .map(dto -> QualityGateCondition.from(dto, idToKeyMetric)) + .collect(MoreCollectors.toList()); + + // Find all conditions that are not present in QUALITY_GATE_CONDITIONS + // Those conditions must be deleted + List qgConditionsToBeDeleted = new ArrayList<>(qualityGateConditions); + qgConditionsToBeDeleted.removeAll(QUALITY_GATE_CONDITIONS); + qgConditionsToBeDeleted.stream() + .forEach(qgc -> qualityGateConditionDao.delete(qgc.toQualityGateDto(builtin.getId()), dbSession)); + + // Find all conditions that are not present in qualityGateConditions + // Those conditions must be created + List qgConditionsToBeCreated = new ArrayList<>(QUALITY_GATE_CONDITIONS); + qgConditionsToBeCreated.removeAll(qualityGateConditions); + qgConditionsToBeCreated.stream() + .forEach(qgc -> + qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), qgc.getMetricKey(), qgc.getOperator(), qgc.getWarningThreshold(), + qgc.getErrorThreshold(), qgc.getPeriod()) + ); + + if (!qgConditionsToBeCreated.isEmpty() || !qgConditionsToBeDeleted.isEmpty()) { + LOGGER.info("Built-in quality gate's conditions of [{}] has been updated", BUILTIN_QUALITY_GATE_NAME); + } } - private void createBuiltinQualityGate(DbSession dbSession) { - String ratingAValue = Integer.toString(RatingGrid.Rating.A.getIndex()); - QualityGateDto builtin = createQualityGate(dbSession, BUILTIN_QUALITY_GATE); - qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), - NEW_SECURITY_RATING_KEY, OPERATOR_GREATER_THAN, null, ratingAValue, LEAK_PERIOD); - qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), - NEW_RELIABILITY_RATING_KEY, OPERATOR_GREATER_THAN, null, ratingAValue, LEAK_PERIOD); - qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), - NEW_MAINTAINABILITY_RATING_KEY, OPERATOR_GREATER_THAN, null, ratingAValue, LEAK_PERIOD); - qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), - NEW_COVERAGE_KEY, OPERATOR_LESS_THAN, null, "80", LEAK_PERIOD); - qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), - NEW_DUPLICATED_LINES_DENSITY_KEY, OPERATOR_GREATER_THAN, null, "3", LEAK_PERIOD); - qualityGates.setDefault(dbSession, builtin.getId()); + @Override + public void stop() { + // do nothing } - private QualityGateDto createQualityGate(DbSession dbSession, String name){ + private QualityGateDto createQualityGate(DbSession dbSession, String name) { QualityGateDto qualityGate = new QualityGateDto() .setName(name) - .setBuiltIn(true); - dbClient.qualityGateDao().insert(dbSession, qualityGate); - return qualityGate; + .setBuiltIn(true) + .setCreatedAt(new Date(system2.now())); + return dbClient.qualityGateDao().insert(dbSession, qualityGate); } - private void registerBuiltinQualityGate(DbSession dbSession) { - loadedTemplateDao.insert(new LoadedTemplateDto(BUILTIN_QUALITY_GATE, LoadedTemplateDto.QUALITY_GATE_TYPE), dbSession); + private static class QualityGateCondition { + private Long id; + private String metricKey; + private Integer period; + private String operator; + private String warningThreshold; + private String errorThreshold; + + public static QualityGateCondition from(QualityGateConditionDto qualityGateConditionDto, Map mapping) { + return new QualityGateCondition() + .setId(qualityGateConditionDto.getId()) + .setMetricKey(mapping.get(qualityGateConditionDto.getMetricId())) + .setOperator(qualityGateConditionDto.getOperator()) + .setPeriod(qualityGateConditionDto.getPeriod()) + .setErrorThreshold(qualityGateConditionDto.getErrorThreshold()) + .setWarningThreshold(qualityGateConditionDto.getWarningThreshold()); + } + + @CheckForNull + public Long getId() { + return id; + } + + public QualityGateCondition setId(Long id) { + this.id = id; + return this; + } + + public String getMetricKey() { + return metricKey; + } + + public QualityGateCondition setMetricKey(String metricKey) { + this.metricKey = metricKey; + return this; + } + + public Integer getPeriod() { + return period; + } + + public QualityGateCondition setPeriod(Integer period) { + this.period = period; + return this; + } + + public String getOperator() { + return operator; + } + + public QualityGateCondition setOperator(String operator) { + this.operator = operator; + return this; + } + + @CheckForNull + public String getWarningThreshold() { + return warningThreshold; + } + + public QualityGateCondition setWarningThreshold(@Nullable String warningThreshold) { + this.warningThreshold = warningThreshold; + return this; + } + + @CheckForNull + public String getErrorThreshold() { + return errorThreshold; + } + + public QualityGateCondition setErrorThreshold(@Nullable String errorThreshold) { + this.errorThreshold = errorThreshold; + return this; + } + + public QualityGateConditionDto toQualityGateDto(long qualityGateId) { + return new QualityGateConditionDto() + .setId(id) + .setMetricKey(metricKey) + .setOperator(operator) + .setPeriod(period) + .setErrorThreshold(errorThreshold) + .setWarningThreshold(warningThreshold) + .setQualityGateId(qualityGateId); + } + + // id does not belongs to equals to be able to be compared with builtin + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QualityGateCondition that = (QualityGateCondition) o; + return Objects.equals(metricKey, that.metricKey) && + Objects.equals(period, that.period) && + Objects.equals(operator, that.operator) && + Objects.equals(warningThreshold, that.warningThreshold) && + Objects.equals(errorThreshold, that.errorThreshold); + } + + // id does not belongs to hashcode to be able to be compared with builtin + @Override + public int hashCode() { + return Objects.hash(metricKey, period, operator, warningThreshold, errorThreshold); + } } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java index 4b4026d5fe6..d40b4672439 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java @@ -80,17 +80,16 @@ public class QualityGateFinderTest { } @Test - public void fail_when_default_qgate_defined_in_properties_does_not_exists() throws Exception { + public void fail_when_default_qgate_defined_in_properties_does_not_exists() { ComponentDto project = db.components().insertPrivateProject(); QualityGateDto dbQualityGate = db.qualityGates().createDefaultQualityGate("Sonar way"); db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession); - expectedException.expect(NotFoundException.class); - underTest.getQualityGate(dbSession, project.getId()); + assertThat(underTest.getQualityGate(dbSession, project.getId())).isEmpty(); } @Test - public void fail_when_project_qgate_defined_in_properties_does_not_exists() throws Exception { + public void fail_when_project_qgate_defined_in_properties_does_not_exists() { ComponentDto project = db.components().insertPrivateProject(); QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate("My team QG"); db.qualityGates().associateProjectToQualityGate(project, dbQualityGate); diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/RegisterQualityGatesTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/RegisterQualityGatesTest.java index 79e661ada9d..731c5ef6129 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/RegisterQualityGatesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/RegisterQualityGatesTest.java @@ -19,29 +19,35 @@ */ package org.sonar.server.qualitygate; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; -import org.sonar.db.loadedtemplate.LoadedTemplateDto; +import org.sonar.db.metric.MetricDao; import org.sonar.db.metric.MetricDto; +import org.sonar.db.qualitygate.QualityGateConditionDao; import org.sonar.db.qualitygate.QualityGateConditionDto; +import org.sonar.db.qualitygate.QualityGateDao; import org.sonar.db.qualitygate.QualityGateDto; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT_KEY; import static org.sonar.api.measures.Metric.ValueType.INT; import static org.sonar.api.measures.Metric.ValueType.PERCENT; import static org.sonar.db.metric.MetricTesting.newMetricDto; @@ -52,32 +58,236 @@ public class RegisterQualityGatesTest { @Rule public DbTester db = DbTester.create(System2.INSTANCE); + @Rule + public LogTester logTester = new LogTester(); + + private static final int LEAK_PERIOD = 1; + private static final String BUILT_IN_NAME = "Sonar way"; + + private DbClient dbClient = db.getDbClient(); + private DbSession dbSession = db.getSession(); + + private QualityGateDao qualityGateDao = dbClient.qualityGateDao(); + private QualityGateConditionDao gateConditionDao = dbClient.gateConditionDao(); + private MetricDao metricDao = dbClient.metricDao(); + private QualityGateConditionsUpdater qualityGateConditionsUpdater = new QualityGateConditionsUpdater(dbClient); + private QualityGateUpdater qualityGateUpdater = new QualityGateUpdater(dbClient); + private QualityGateFinder qualityGateFinder = new QualityGateFinder(dbClient); + + private RegisterQualityGates underTest = new RegisterQualityGates(dbClient, qualityGateUpdater, qualityGateConditionsUpdater, qualityGateFinder, System2.INSTANCE); - DbClient dbClient = db.getDbClient(); - DbSession dbSession = db.getSession(); + @Before + public void setup() { + insertMetrics(); + } - QualityGates qualityGates = mock(QualityGates.class); - RegisterQualityGates task = new RegisterQualityGates(dbClient, - new QualityGateConditionsUpdater(dbClient), - dbClient.loadedTemplateDao(), - qualityGates); + @After + public void after() { + underTest.stop(); + } @Test public void register_default_gate() { - MetricDto newReliability = dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_RELIABILITY_RATING_KEY).setValueType(INT.name()).setHidden(false)); - MetricDto newSecurity = dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_SECURITY_RATING_KEY).setValueType(INT.name()).setHidden(false)); - MetricDto newMaintainability = dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_MAINTAINABILITY_RATING_KEY).setValueType(PERCENT.name()).setHidden(false)); - MetricDto newCoverage = dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_COVERAGE_KEY).setValueType(PERCENT.name()).setHidden(false)); - MetricDto newDuplication = dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_DUPLICATED_LINES_DENSITY_KEY).setValueType(PERCENT.name()).setHidden(false)); + underTest.start(); + + verifyCorrectBuiltInQualityGate(); + + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate [Sonar way] has been created") + ).isTrue(); + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate's conditions of [Sonar way] has been updated") + ).isTrue(); + } + + @Test + public void upgrade_empty_quality_gate() { + QualityGateDto builtin = new QualityGateDto().setName(BUILT_IN_NAME).setBuiltIn(true); + qualityGateDao.insert(dbSession, builtin); + dbSession.commit(); + + underTest.start(); + assertThat(qualityGateDao.selectAll(dbSession)).hasSize(1); + verifyCorrectBuiltInQualityGate(); + + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate's conditions of [Sonar way] has been updated") + ).isTrue(); + } + + @Test + public void upgrade_should_remove_deleted_condition() { + QualityGateDto builtin = new QualityGateDto().setName(BUILT_IN_NAME).setBuiltIn(true); + qualityGateDao.insert(dbSession, builtin); + + createBuiltInConditions(builtin); + + // Add another condition + qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), + NEW_SECURITY_REMEDIATION_EFFORT_KEY, OPERATOR_GREATER_THAN, null, "5", LEAK_PERIOD); + + dbSession.commit(); + + underTest.start(); + assertThat(qualityGateDao.selectAll(dbSession)).hasSize(1); + verifyCorrectBuiltInQualityGate(); + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate's conditions of [Sonar way] has been updated") + ).isTrue(); + } + + @Test + public void upgrade_should_add_missing_condition() { + QualityGateDto builtin = new QualityGateDto().setName(BUILT_IN_NAME).setBuiltIn(true); + qualityGateDao.insert(dbSession, builtin); + + List builtInConditions = createBuiltInConditions(builtin); + + // Remove a condition + QualityGateConditionDto conditionToBeDeleted = builtInConditions.get(new Random().nextInt(builtInConditions.size())); + gateConditionDao.delete(conditionToBeDeleted, dbSession); + + dbSession.commit(); + + underTest.start(); + assertThat(qualityGateDao.selectAll(dbSession)).hasSize(1); + verifyCorrectBuiltInQualityGate(); + + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate's conditions of [Sonar way] has been updated") + ).isTrue(); + } + + @Test + public void should_set_SonarWay_as_builtin_when_not_set() { + QualityGateDto builtin = new QualityGateDto().setName(BUILT_IN_NAME).setBuiltIn(false); + qualityGateDao.insert(dbSession, builtin); + + createBuiltInConditions(builtin); + dbSession.commit(); + + underTest.start(); + assertThat(qualityGateDao.selectAll(dbSession)).hasSize(1); + verifyCorrectBuiltInQualityGate(); + + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Quality gate [Sonar way] has been set as built-in") + ).isTrue(); + } + + @Test + public void should_not_update_builtin_quality_gate_if_already_uptodate() { + QualityGateDto builtin = new QualityGateDto().setName(BUILT_IN_NAME).setBuiltIn(true); + qualityGateDao.insert(dbSession, builtin); + + createBuiltInConditions(builtin); dbSession.commit(); - task.start(); + underTest.start(); + assertThat(qualityGateDao.selectAll(dbSession)).hasSize(1); + verifyCorrectBuiltInQualityGate(); + + // Log must not be present + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Quality gate [Sonar way] has been set as built-in") + ).isFalse(); + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate [Sonar way] has been created") + ).isFalse(); + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate's conditions of [Sonar way] has been updated") + ).isFalse(); + } + + @Test + public void ensure_only_one_built_in_quality_gate() { + String qualityGateName = "IncorrectQualityGate"; + QualityGateDto builtin = new QualityGateDto().setName(qualityGateName).setBuiltIn(true); + qualityGateDao.insert(dbSession, builtin); + dbSession.commit(); + + underTest.start(); + + QualityGateDto oldQualityGate = qualityGateDao.selectByName(dbSession, qualityGateName); + assertThat(oldQualityGate).isNotNull(); + assertThat(oldQualityGate.isBuiltIn()).isFalse(); + + List allBuiltInQualityProfiles = qualityGateDao.selectAll(dbSession) + .stream() + .filter(QualityGateDto::isBuiltIn) + .collect(MoreCollectors.toList()); + assertThat(allBuiltInQualityProfiles) + .extracting("name") + .containsExactly(BUILT_IN_NAME); + + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate [Sonar way] has been created") + ).isTrue(); + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate's conditions of [Sonar way] has been updated") + ).isTrue(); + } + + @Test + public void ensure_only_that_builtin_is_set_as_default_when_no_default_quality_gate() { + QualityGateDto builtin = new QualityGateDto().setName(BUILT_IN_NAME).setBuiltIn(true); + qualityGateDao.insert(dbSession, builtin); + dbSession.commit(); - assertThat(dbClient.loadedTemplateDao().countByTypeAndKey("QUALITY_GATE", "SonarQube way", dbSession)).isEqualTo(1); - QualityGateDto qualityGateDto = dbClient.qualityGateDao().selectByName(dbSession, "SonarQube way"); + underTest.start(); + + assertThat(qualityGateFinder.getDefault(dbSession)).isPresent(); + assertThat(qualityGateFinder.getDefault(dbSession).get().getId()).isEqualTo(builtin.getId()); + + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate [Sonar way] has been set as default") + ).isTrue(); + } + + @Test + public void builtin_quality_gate_with_incorrect_metricId_should_not_throw_an_exception() { + QualityGateDto builtin = new QualityGateDto().setName(BUILT_IN_NAME).setBuiltIn(true); + qualityGateDao.insert(dbSession, builtin); + QualityGateConditionDto conditionDto = new QualityGateConditionDto() + .setMetricId(-1) // This Id does not exist + .setOperator(OPERATOR_GREATER_THAN) + .setErrorThreshold("1") + .setWarningThreshold("1"); + gateConditionDao.insert(conditionDto,dbSession); + dbSession.commit(); + + underTest.start(); + + // No exception thrown + verifyCorrectBuiltInQualityGate(); + + assertThat( + logTester.logs(LoggerLevel.INFO).contains("Built-in quality gate's conditions of [Sonar way] has been updated") + ).isTrue(); + } + + private void insertMetrics() { + dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_RELIABILITY_RATING_KEY).setValueType(INT.name()).setHidden(false)); + dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_SECURITY_RATING_KEY).setValueType(INT.name()).setHidden(false)); + dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_SECURITY_REMEDIATION_EFFORT_KEY).setValueType(INT.name()).setHidden(false)); + dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_MAINTAINABILITY_RATING_KEY).setValueType(PERCENT.name()).setHidden(false)); + dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_COVERAGE_KEY).setValueType(PERCENT.name()).setHidden(false)); + dbClient.metricDao().insert(dbSession, newMetricDto().setKey(NEW_DUPLICATED_LINES_DENSITY_KEY).setValueType(PERCENT.name()).setHidden(false)); + dbSession.commit(); + } + + private void verifyCorrectBuiltInQualityGate() { + MetricDto newReliability = metricDao.selectByKey(dbSession, NEW_RELIABILITY_RATING_KEY); + MetricDto newSecurity = metricDao.selectByKey(dbSession, NEW_SECURITY_RATING_KEY); + MetricDto newMaintainability = metricDao.selectByKey(dbSession, NEW_MAINTAINABILITY_RATING_KEY); + MetricDto newCoverage = metricDao.selectByKey(dbSession, NEW_COVERAGE_KEY); + MetricDto newDuplication = metricDao.selectByKey(dbSession, NEW_DUPLICATED_LINES_DENSITY_KEY); + + QualityGateDto qualityGateDto = qualityGateDao.selectByName(dbSession, BUILT_IN_NAME); assertThat(qualityGateDto).isNotNull(); + assertThat(qualityGateDto.getCreatedAt()).isNotNull(); assertThat(qualityGateDto.isBuiltIn()).isTrue(); - assertThat(dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateDto.getId())) + assertThat(gateConditionDao.selectForQualityGate(dbSession, qualityGateDto.getId())) .extracting(QualityGateConditionDto::getMetricId, QualityGateConditionDto::getOperator, QualityGateConditionDto::getWarningThreshold, QualityGateConditionDto::getErrorThreshold, QualityGateConditionDto::getPeriod) .containsOnly( @@ -86,21 +296,22 @@ public class RegisterQualityGatesTest { tuple(newMaintainability.getId().longValue(), OPERATOR_GREATER_THAN, null, "1", 1), tuple(newCoverage.getId().longValue(), OPERATOR_LESS_THAN, null, "80", 1), tuple(newDuplication.getId().longValue(), OPERATOR_GREATER_THAN, null, "3", 1)); - verify(qualityGates).setDefault(any(DbSession.class), anyLong()); - - task.stop(); } - @Test - public void does_not_register_default_gate_if_already_executed() { - String templateType = "QUALITY_GATE"; - String templateName = "SonarQube way"; - dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(templateName, templateType), dbSession); - dbSession.commit(); + private List createBuiltInConditions(QualityGateDto builtin) { + List conditions = new ArrayList<>(); - task.start(); + conditions.add(qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), + NEW_SECURITY_RATING_KEY, OPERATOR_GREATER_THAN, null, "1", LEAK_PERIOD)); + conditions.add(qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), + NEW_RELIABILITY_RATING_KEY, OPERATOR_GREATER_THAN, null, "1", LEAK_PERIOD)); + conditions.add(qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), + NEW_MAINTAINABILITY_RATING_KEY, OPERATOR_GREATER_THAN, null, "1", LEAK_PERIOD)); + conditions.add(qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), + NEW_COVERAGE_KEY, OPERATOR_LESS_THAN, null, "80", LEAK_PERIOD)); + conditions.add(qualityGateConditionsUpdater.createCondition(dbSession, builtin.getId(), + NEW_DUPLICATED_LINES_DENSITY_KEY, OPERATOR_GREATER_THAN, null, "3", LEAK_PERIOD)); - assertThat(dbClient.qualityGateDao().selectAll(dbSession)).isEmpty(); - verifyZeroInteractions(qualityGates); + return conditions; } } diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/OrganizationQualityGateUiTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/OrganizationQualityGateUiTest.java index d311e4aab1b..30fff3e7545 100644 --- a/tests/src/test/java/org/sonarqube/tests/qualityGate/OrganizationQualityGateUiTest.java +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/OrganizationQualityGateUiTest.java @@ -112,7 +112,7 @@ public class OrganizationQualityGateUiTest { ProjectDashboardPage page = tester.openBrowser() .logIn().submitCredentials(user.getLogin()) .openProjectDashboard(project); - page.hasQualityGateLink("SonarQube way", link); + page.hasQualityGateLink("Sonar way", link); } @Test @@ -120,6 +120,6 @@ public class OrganizationQualityGateUiTest { QualityGatePage page = tester.openBrowser() .logIn().submitCredentials(user.getLogin()) .openQualityGates(organization.getKey()); - page.countQualityGates(1).displayQualityGateDetail("SonarQube way"); + page.countQualityGates(1).displayQualityGateDetail("Sonar way"); } } diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java index 0baa09641a2..859f6b479fc 100644 --- a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java @@ -113,7 +113,7 @@ public class QualityGateUiTest { .logIn().submitCredentials(login) .openQualityGates() .canNotCreateQG() - .displayQualityGateDetail("SonarQube way"); + .displayQualityGateDetail("Sonar way"); tester.openBrowser() .logIn().submitCredentials(admin) .openQualityGates() diff --git a/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java b/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java index b56680b7299..b88d64da715 100644 --- a/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java +++ b/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java @@ -113,7 +113,7 @@ public class WebhooksTest { assertThat(project.get("name")).isEqualTo(PROJECT_NAME); assertThat(project.get("url")).isEqualTo(orchestrator.getServer().getUrl() + "/dashboard?id=" + PROJECT_KEY); Map gate = (Map) payload.get("qualityGate"); - assertThat(gate.get("name")).isEqualTo("SonarQube way"); + assertThat(gate.get("name")).isEqualTo("Sonar way"); assertThat(gate.get("status")).isEqualTo("OK"); assertThat(gate.get("conditions")).isNotNull(); diff --git a/tests/src/test/resources/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html b/tests/src/test/resources/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html index ac440b184aa..12a1ea27125 100644 --- a/tests/src/test/resources/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html +++ b/tests/src/test/resources/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html @@ -42,7 +42,7 @@ waitForText id=content - *Quality Gate*SonarQube way* + *Quality Gate*Sonar way* waitForText -- 2.39.5