]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10089 Automatically update built-in quality gate
authorEric Hartmann <hartmann.eric@gmail.com>
Wed, 22 Nov 2017 00:49:15 +0000 (01:49 +0100)
committerEric Hartmann <hartmann.eric@gmail.Com>
Mon, 4 Dec 2017 12:44:55 +0000 (13:44 +0100)
15 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/RegisterQualityGatesTest.java
tests/src/test/java/org/sonarqube/tests/qualityGate/OrganizationQualityGateUiTest.java
tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java
tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java
tests/src/test/resources/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html

index b8646499c6c03393dcb601b1edc9d771e54d2cc9..e099e96b426b4960719544a4f1450d290335cc3d 100644 (file)
@@ -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;
index d3d0bbe5bac15f0e606b7e1167687304b664312a..7f108092ace4e1bf31d85db15b0359db8de4e4e4 100644 (file)
@@ -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;
   }
index 519e0dcb7bc99ed5fc1362d4eea474dcad0904a3..345538bfc8abe49c63390088a74b5a1e431fc285 100644 (file)
@@ -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);
   }
index 77548197d02dfb9bf5b2998ea8238bec2cbc479d..9347308d3c5b5b4a64b18f0b73edec6693c37058 100644 (file)
@@ -34,4 +34,6 @@ public interface QualityGateMapper {
   void delete(long id);
 
   void update(QualityGateDto qGate);
+
+  void ensureOneBuiltInQualityGate(String builtInQualityName);
 }
index 290a4966886e38e00ea52f8b3076839825df019d..332414e8b710865c4634d76543c405a84c0cd79b 100644 (file)
   <update id="update" parameterType="QualityGate">
     update quality_gates set
     name=#{name},
+    is_built_in=#{isBuiltIn},
     updated_at=#{updatedAt}
     where id=#{id}
   </update>
 
+  <update id="ensureOneBuiltInQualityGate" parameterType="string">
+    UPDATE quality_gates
+    SET
+      is_built_in=${_false}
+    WHERE
+      is_built_in=${_true} AND name &lt;&gt; #{builtInQualityName}
+  </update>
+
 </mapper>
 
index ace49296fe0974ccbe414d5ea01b02d281f81cfc..b6b3ad0d8d1bfabb106cdf601006690232b8e9cd 100644 (file)
 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<QualityGateDto> 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<QualityGateDto> getDefault(DbSession dbSession) {
+    Optional<Long> 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<Long> 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 {
index 722e593c58668237087a33823b8f9b23557241a9..4115a651a532b7461f5cfb77b2ea5b92f0d628a8 100644 (file)
@@ -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<String> errors = new ArrayList<>();
     if (isNullOrEmpty(name)) {
index 941acb3ac305fb3df67d92e08bbb09c5afd6b870..2fc1da81afc4d4c38b7553034af396c816b4484c 100644 (file)
@@ -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);
index 05394b65ed690df5f8ecd03703324e2dca016588..8e79e62d61ea9129b22fe0cb2ba1dee120cc4cb9 100644 (file)
  */
 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<QualityGateCondition> 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<Long, String> 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<QualityGateCondition> 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<QualityGateCondition> 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<QualityGateCondition> 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<Long, String> 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);
+    }
   }
 }
index 4b4026d5fe69d51bc2e79b1350cd87f94fc8d558..d40b4672439b982f96379fb57abfb44232ef111c 100644 (file)
@@ -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);
index 79e661ada9d2b6c92b92db07f3773fb340efa844..731c5ef61295cb979d4f4a15dfd7e73ac66a46d8 100644 (file)
  */
 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<QualityGateConditionDto> 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<QualityGateDto> 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<QualityGateConditionDto> createBuiltInConditions(QualityGateDto builtin) {
+    List<QualityGateConditionDto> 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;
   }
 }
index d311e4aab1bab1c0b752cf699f0fb1ed46110c45..30fff3e754559b52b83f28738f1864eec2c910ed 100644 (file)
@@ -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");
   }
 }
index 0baa09641a2568747de63d973ecd61e79d735188..859f6b479fc2e214112d3db211b956df411f0314 100644 (file)
@@ -113,7 +113,7 @@ public class QualityGateUiTest {
       .logIn().submitCredentials(login)
       .openQualityGates()
       .canNotCreateQG()
-      .displayQualityGateDetail("SonarQube way");
+      .displayQualityGateDetail("Sonar way");
     tester.openBrowser()
       .logIn().submitCredentials(admin)
       .openQualityGates()
index b56680b72998d92a30854713ba3c064cc805c3ae..b88d64da7155424c600798047ed2a27cc33fa033 100644 (file)
@@ -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<String, Object> gate = (Map<String, Object>) 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();
 
index ac440b184aa21e6981ddf2491d8d96b0e639d11d..12a1ea27125259a6758386b06b219fe53b3187c8 100644 (file)
@@ -42,7 +42,7 @@
   <tr>
     <td>waitForText</td>
     <td>id=content</td>
-    <td>*Quality Gate*SonarQube way*</td>
+    <td>*Quality Gate*Sonar way*</td>
   </tr>
   <tr>
     <td>waitForText</td>