]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8287 Feed measures in project measures index
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 18 Oct 2016 10:30:25 +0000 (12:30 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 18 Oct 2016 15:01:46 +0000 (17:01 +0200)
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresDoc.java
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresResultSetIterator.java
server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresResultSetIteratorTest.java

index a844c817e16fda73713a32e4538eb66a22361f2d..48dbe0e5203023233818d9a4edc38bea9ae014d8 100644 (file)
  */
 package org.sonar.server.component.es;
 
+import com.google.common.collect.ImmutableMap;
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.sonar.core.util.stream.Collectors;
 import org.sonar.server.es.BaseDoc;
 
 import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
@@ -91,4 +93,15 @@ public class ProjectMeasuresDoc extends BaseDoc {
     setField(FIELD_MEASURES, measures);
     return this;
   }
+
+  public ProjectMeasuresDoc setMeasuresFromMap(Map<String, Object> measures) {
+    setMeasures(
+      measures.entrySet().stream()
+        .map(entry -> ImmutableMap.of(
+          ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY, entry.getKey(),
+          ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE, entry.getValue()))
+        .collect(Collectors.toList()));
+    return this;
+  }
+
 }
index 72d8bbb4874135f34ed6c9fb007808e197648d83..845a66a7c261beed8d50c50eb60d05942dbcdf5a 100644 (file)
 
 package org.sonar.server.component.es;
 
+import com.google.common.base.Joiner;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.resources.Qualifiers;
@@ -32,16 +35,15 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.ResultSetIterator;
 
+import static org.sonar.api.measures.Metric.ValueType.DATA;
+import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
+import static org.sonar.db.DatabaseUtils.repeatCondition;
+
 public class ProjectMeasuresResultSetIterator extends ResultSetIterator<ProjectMeasuresDoc> {
 
-  private static final String[] FIELDS = {
-    "p.uuid",
-    "p.kee",
-    "p.name",
-    "s.created_at"
-  };
+  private static final Joiner METRICS_JOINER = Joiner.on("','");
 
-  private static final String SQL_ALL = "SELECT " + StringUtils.join(FIELDS, ",") + " FROM projects p " +
+  private static final String SQL_PROJECTS = "SELECT p.uuid, p.kee, p.name, s.uuid, s.created_at FROM projects p " +
     "LEFT OUTER JOIN snapshots s ON s.component_uuid=p.uuid AND s.islast=? " +
     "WHERE p.enabled=? AND p.scope=? AND p.qualifier=?";
 
@@ -49,13 +51,38 @@ public class ProjectMeasuresResultSetIterator extends ResultSetIterator<ProjectM
 
   private static final String PROJECT_FILTER = " AND p.uuid=?";
 
-  private ProjectMeasuresResultSetIterator(PreparedStatement stmt) throws SQLException {
+  private static final String SQL_METRICS = "SELECT m.id, m.name FROM metrics m " +
+    "WHERE m.val_type NOT IN ('" + METRICS_JOINER.join(DATA.name(), DISTRIB.name()) + "') " +
+    "AND m.enabled=? AND m.hidden=?";
+
+  private static final String SQL_MEASURES = "SELECT pm.metric_id, pm.value, pm.variation_value_1 FROM project_measures pm " +
+    "WHERE pm.component_uuid = ? AND pm.analysis_uuid = ? " +
+    "AND pm.metric_id IN ({metricIds}) " +
+    "AND (pm.value IS NOT NULL OR pm.variation_value_1 IS NOT NULL) " +
+    "AND pm.person_id IS NULL ";
+
+  private final DbSession dbSession;
+  private final Map<Long, String> metrics;
+
+  private ProjectMeasuresResultSetIterator(PreparedStatement stmt, DbSession dbSession, Map<Long, String> metrics) throws SQLException {
     super(stmt);
+    this.dbSession = dbSession;
+    this.metrics = metrics;
   }
 
   static ProjectMeasuresResultSetIterator create(DbClient dbClient, DbSession session, long afterDate, @Nullable String projectUuid) {
     try {
-      String sql = SQL_ALL;
+      PreparedStatement projectsStatement = createProjectsStatement(dbClient, session, afterDate, projectUuid);
+      Map<Long, String> metricIds = selectMetricIds(session);
+      return new ProjectMeasuresResultSetIterator(projectsStatement, session, metricIds);
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to execute request to select all project measures", e);
+    }
+  }
+
+  private static PreparedStatement createProjectsStatement(DbClient dbClient, DbSession session, long afterDate, @Nullable String projectUuid) {
+    try {
+      String sql = SQL_PROJECTS;
       sql += afterDate <= 0L ? "" : DATE_FILTER;
       sql += projectUuid == null ? "" : PROJECT_FILTER;
       PreparedStatement stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
@@ -71,20 +98,83 @@ public class ProjectMeasuresResultSetIterator extends ResultSetIterator<ProjectM
       if (projectUuid != null) {
         stmt.setString(index, projectUuid);
       }
-      return new ProjectMeasuresResultSetIterator(stmt);
+      return stmt;
     } catch (SQLException e) {
       throw new IllegalStateException("Fail to prepare SQL request to select all project measures", e);
     }
   }
 
+  private static Map<Long, String> selectMetricIds(DbSession session) {
+    Map<Long, String> metrics = new HashMap<>();
+    try (PreparedStatement stmt = createMetricsStatement(session);
+      ResultSet rs = stmt.executeQuery()) {
+      while (rs.next()) {
+        metrics.put(rs.getLong(1), rs.getString(2));
+      }
+      return metrics;
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to execute request to select all metrics", e);
+    }
+  }
+
+  private static PreparedStatement createMetricsStatement(DbSession session) throws SQLException {
+    PreparedStatement stmt = session.getConnection().prepareStatement(SQL_METRICS);
+    stmt.setBoolean(1, true);
+    stmt.setBoolean(2, false);
+    return stmt;
+  }
+
   @Override
   protected ProjectMeasuresDoc read(ResultSet rs) throws SQLException {
+    String projectUuid = rs.getString(1);
     ProjectMeasuresDoc doc = new ProjectMeasuresDoc()
-      .setId(rs.getString(1))
+      .setId(projectUuid)
       .setKey(rs.getString(2))
-      .setName(rs.getString(3));
-    long analysisDate = rs.getLong(4);
+      .setName(rs.getString(3))
+      .setMeasuresFromMap(selectMeasures(projectUuid, rs.getString(4)));
+    long analysisDate = rs.getLong(5);
     doc.setAnalysedAt(rs.wasNull() ? null : new Date(analysisDate));
     return doc;
   }
+
+  private Map<String, Object> selectMeasures(String projectUuid, String analysisUuid) {
+    Map<String, Object> measures = new HashMap<>();
+    try (PreparedStatement stmt = createMeasuresStatement(projectUuid, analysisUuid);
+      ResultSet rs = stmt.executeQuery()) {
+      while (rs.next()) {
+        String metricKey = metrics.get(rs.getLong(1));
+        Double value = metricKey.startsWith("new_") ? getDouble(rs, 3) : getDouble(rs, 2);
+        measures.put(metricKey, value);
+      }
+      return measures;
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to execute request to select measures", e);
+    }
+  }
+
+  private PreparedStatement createMeasuresStatement(String projectUuid, String analysisUuid) throws SQLException {
+    String sql = StringUtils.replace(SQL_MEASURES, "{metricIds}", repeatCondition("?", metrics.size(), ","));
+    PreparedStatement stmt = dbSession.getConnection().prepareStatement(sql);
+    stmt.setString(1, projectUuid);
+    stmt.setString(2, analysisUuid);
+    int index = 3;
+    for (Long metricId : metrics.keySet()) {
+      stmt.setLong(index, metricId);
+      index++;
+    }
+    return stmt;
+  }
+
+  private static Double getDouble(ResultSet rs, int index) {
+    try {
+      Double value = rs.getDouble(index);
+      if (!rs.wasNull()) {
+        return value;
+      }
+      throw new IllegalStateException("No value");
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to get double value", e);
+    }
+  }
+
 }
index ce436bf5354bf47341525a990182da03f3ce9c82..b70092e911643fcc935bb0ac8a08b12d7976bc62 100644 (file)
 
 package org.sonar.server.component.es;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import java.util.Date;
 import java.util.Map;
 import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -33,8 +36,15 @@ import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.measure.MeasureTesting;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.metric.MetricTesting;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.Metric.ValueType.DATA;
+import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
+import static org.sonar.api.measures.Metric.ValueType.INT;
 import static org.sonar.db.component.ComponentTesting.newDeveloper;
 import static org.sonar.db.component.ComponentTesting.newProjectDto;
 import static org.sonar.db.component.ComponentTesting.newView;
@@ -42,6 +52,9 @@ import static org.sonar.db.component.SnapshotTesting.newAnalysis;
 
 public class ProjectMeasuresResultSetIteratorTest {
 
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
@@ -51,9 +64,13 @@ public class ProjectMeasuresResultSetIteratorTest {
   ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
 
   @Test
-  public void return_one_project_measure() {
+  public void return_project_measure() {
+    MetricDto metric1 = insertIntMetric("ncloc");
+    MetricDto metric2 = insertIntMetric("coverage");
     ComponentDto project = newProjectDto().setKey("Project-Key").setName("Project Name");
     SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, metric1, 10d);
+    insertMeasure(project, analysis, metric2, 20d);
 
     Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
 
@@ -64,6 +81,65 @@ public class ProjectMeasuresResultSetIteratorTest {
     assertThat(doc.getKey()).isEqualTo("Project-Key");
     assertThat(doc.getName()).isEqualTo("Project Name");
     assertThat(doc.getAnalysedAt()).isNotNull().isEqualTo(new Date(analysis.getCreatedAt()));
+    assertThat(doc.getMeasures()).containsOnly(
+      ImmutableMap.of("key", "ncloc", "value", 10d),
+      ImmutableMap.of("key", "coverage", "value", 20d));
+  }
+
+  @Test
+  public void return_project_measure_having_leak() throws Exception {
+    MetricDto metric = insertIntMetric("new_lines");
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasureOnLeak(project, analysis, metric, 10d);
+
+    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
+
+    assertThat(docsById).hasSize(1);
+    ProjectMeasuresDoc doc = docsById.get(project.uuid());
+    assertThat(doc).isNotNull();
+    assertThat(doc.getMeasures()).containsOnly(ImmutableMap.of("key", "new_lines", "value", 10d));
+  }
+
+  @Test
+  public void does_not_return_none_numeric_metrics() throws Exception {
+    MetricDto dataMetric = insertMetric("data", DATA);
+    MetricDto distribMetric = insertMetric("distrib", DISTRIB);
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, dataMetric, 10d);
+    insertMeasure(project, analysis, distribMetric, 10d);
+
+    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
+    assertThat(docsById).hasSize(1);
+    ProjectMeasuresDoc doc = docsById.get(project.uuid());
+    assertThat(doc.getMeasures()).isEmpty();
+  }
+
+  @Test
+  public void does_not_return_disabled_and_hidden_metrics() throws Exception {
+    MetricDto disabledMetric = insertMetric("disabled", false, false, INT);
+    MetricDto hiddenMetric = insertMetric("hidden", true, true, INT);
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, disabledMetric, 10d);
+    insertMeasure(project, analysis, hiddenMetric, 10d);
+
+    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
+    assertThat(docsById).hasSize(1);
+    ProjectMeasuresDoc doc = docsById.get(project.uuid());
+    assertThat(doc.getMeasures()).isEmpty();
+  }
+
+  @Test
+  public void fail_when_measure_return_no_value() throws Exception {
+    MetricDto metric = insertIntMetric("new_lines");
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, metric, 10d);
+
+    expectedException.expect(IllegalStateException.class);
+    createResultSetAndReturnDocsById();
   }
 
   @Test
@@ -165,4 +241,38 @@ public class ProjectMeasuresResultSetIteratorTest {
     assertThat(createResultSetAndReturnDocsById()).isEmpty();
   }
 
+  private MetricDto insertIntMetric(String metricKey) {
+    return insertMetric(metricKey, true, false, INT);
+  }
+
+  private MetricDto insertMetric(String metricKey, Metric.ValueType type) {
+    return insertMetric(metricKey, true, false, type);
+  }
+
+  private MetricDto insertMetric(String metricKey, boolean enabled, boolean hidden, Metric.ValueType type) {
+    MetricDto metric = dbClient.metricDao().insert(dbSession,
+      MetricTesting.newMetricDto()
+        .setKey(metricKey)
+        .setEnabled(enabled)
+        .setHidden(hidden)
+        .setValueType(type.name()));
+    dbSession.commit();
+    return metric;
+  }
+
+  private MeasureDto insertMeasure(ComponentDto project, SnapshotDto analysis, MetricDto metric, double value) {
+    return insertMeasure(project, analysis, metric, value, null);
+  }
+
+  private MeasureDto insertMeasureOnLeak(ComponentDto project, SnapshotDto analysis, MetricDto metric, double value) {
+    return insertMeasure(project, analysis, metric, null, value);
+  }
+
+  private MeasureDto insertMeasure(ComponentDto project, SnapshotDto analysis, MetricDto metric, @Nullable Double value, @Nullable Double leakValue) {
+    MeasureDto measure = MeasureTesting.newMeasureDto(metric, project, analysis).setValue(value).setVariation(1, leakValue);
+    dbClient.measureDao().insert(dbSession, measure);
+    dbSession.commit();
+    return measure;
+  }
+
 }