aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Schwarz <daniel.schwarz@sonarsource.com>2017-05-24 12:46:26 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-06-09 08:26:48 +0200
commit884e34739f55e058819001936945e89b41035ffa (patch)
treec57f8840b71ebdba5fa0bd783eb37e900870f205
parent2fb2871f4be5ea216ab71e67b80622c040fd1791 (diff)
downloadsonarqube-884e34739f55e058819001936945e89b41035ffa.tar.gz
sonarqube-884e34739f55e058819001936945e89b41035ffa.zip
SONAR-9230 only index project measures, that will actually be used
-rw-r--r--it/it-tests/src/test/java/it/projectSearch/SearchProjectsTest.java2
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java16
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java119
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java8
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidator.java71
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java26
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java266
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java38
10 files changed, 323 insertions, 228 deletions
diff --git a/it/it-tests/src/test/java/it/projectSearch/SearchProjectsTest.java b/it/it-tests/src/test/java/it/projectSearch/SearchProjectsTest.java
index cf94dbdf026..ec1bdc68de1 100644
--- a/it/it-tests/src/test/java/it/projectSearch/SearchProjectsTest.java
+++ b/it/it-tests/src/test/java/it/projectSearch/SearchProjectsTest.java
@@ -89,7 +89,7 @@ public class SearchProjectsTest {
analyzeProject(projectKey, "shared/xoo-sample");
verifyFilterMatches(projectKey, "ncloc > 1");
- verifyFilterMatches(projectKey, "ncloc > 1 and comment_lines < 10000");
+ verifyFilterMatches(projectKey, "ncloc > 1 and duplicated_lines_density <= 100");
verifyFilterDoesNotMatch("ncloc <= 1");
}
diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java b/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java
index 46a8c91b7d0..777ca12b489 100644
--- a/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java
+++ b/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java
@@ -20,12 +20,14 @@
package org.sonar.db;
import com.google.common.base.Function;
+import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
@@ -39,6 +41,7 @@ import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.IntFunction;
+import java.util.function.Supplier;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.utils.log.Logger;
@@ -333,4 +336,17 @@ public class DatabaseUtils {
public static IllegalStateException wrapSqlException(SQLException e, String message, Object... messageArgs) {
return new IllegalStateException(format(message, messageArgs), e);
}
+
+ /**
+ * This method can be used as a method reference, for not to have to handle the checked exception {@link SQLException}
+ */
+ public static Consumer<String> setStrings(PreparedStatement stmt, Supplier<Integer> index) {
+ return value -> {
+ try {
+ stmt.setString(index.get(), value);
+ } catch (SQLException e) {
+ Throwables.propagate(e);
+ }
+ };
+ }
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java
index 11741d794fe..490899e82a3 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java
@@ -19,49 +19,54 @@
*/
package org.sonar.db.measure;
-import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
+import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.core.util.CloseableIterator;
import org.sonar.db.DatabaseUtils;
import org.sonar.db.DbSession;
+import static java.util.Arrays.asList;
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
-import static org.sonar.api.measures.Metric.ValueType.BOOL;
-import static org.sonar.api.measures.Metric.ValueType.FLOAT;
-import static org.sonar.api.measures.Metric.ValueType.INT;
-import static org.sonar.api.measures.Metric.ValueType.LEVEL;
-import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
-import static org.sonar.api.measures.Metric.ValueType.PERCENT;
-import static org.sonar.api.measures.Metric.ValueType.RATING;
-import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
import static org.sonar.api.utils.KeyValueFormat.parseStringInt;
-import static org.sonar.db.DatabaseUtils.repeatCondition;
import static org.sonar.db.component.DbTagsReader.readDbTags;
public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMeasuresIndexerIterator.ProjectMeasures> {
- private static final Set<String> METRIC_TYPES = ImmutableSet.of(INT.name(), FLOAT.name(), PERCENT.name(), BOOL.name(), MILLISEC.name(), LEVEL.name(), RATING.name(),
- WORK_DUR.name());
-
- private static final Joiner METRICS_JOINER = Joiner.on("','");
+ public static final Set<String> METRIC_KEYS = new HashSet<>(asList(
+ CoreMetrics.NCLOC_KEY,
+ CoreMetrics.DUPLICATED_LINES_DENSITY_KEY,
+ CoreMetrics.COVERAGE_KEY,
+ CoreMetrics.SQALE_RATING_KEY,
+ CoreMetrics.RELIABILITY_RATING_KEY,
+ CoreMetrics.SECURITY_RATING_KEY,
+ CoreMetrics.ALERT_STATUS_KEY,
+ CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY,
+ CoreMetrics.NEW_SECURITY_RATING_KEY,
+ CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY,
+ CoreMetrics.NEW_COVERAGE_KEY,
+ CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY,
+ CoreMetrics.NEW_LINES_KEY,
+ CoreMetrics.NEW_RELIABILITY_RATING_KEY));
private static final String SQL_PROJECTS = "SELECT p.organization_uuid, p.uuid, p.kee, p.name, s.uuid, s.created_at, p.tags " +
"FROM projects p " +
@@ -70,57 +75,37 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
private static final String PROJECT_FILTER = " AND p.uuid=?";
- private static final String SQL_METRICS = "SELECT m.id, m.name FROM metrics m " +
- "WHERE (m.val_type IN ('" + METRICS_JOINER.join(METRIC_TYPES) + "') OR m.name=?)" +
- "AND m.enabled=?";
-
- private static final String SQL_MEASURES = "SELECT pm.metric_id, pm.value, pm.variation_value_1, pm.text_value FROM project_measures pm " +
+ private static final String SQL_MEASURES = "SELECT m.name, pm.value, pm.variation_value_1, pm.text_value FROM project_measures pm " +
+ "INNER JOIN metrics m ON m.id = pm.metric_id " +
"WHERE pm.component_uuid = ? AND pm.analysis_uuid = ? " +
- "AND pm.metric_id IN ({metricIds}) " +
+ "AND m.name IN ({metricNames}) " +
"AND (pm.value IS NOT NULL OR pm.variation_value_1 IS NOT NULL OR pm.text_value IS NOT NULL) " +
- "AND pm.person_id IS NULL ";
+ "AND pm.person_id IS NULL " +
+ "AND m.enabled = ? ";
+ private static final boolean ENABLED = true;
+ private static final int FIELD_METRIC_NAME = 1;
+ private static final int FIELD_MEASURE_VALUE = 2;
+ private static final int FIELD_MEASURE_VARIATION_VALUE_1 = 3;
+ private static final int FIELD_MEASURE_TEXT_VALUE = 4;
private final PreparedStatement measuresStatement;
- private final Map<Long, String> metricKeysByIds;
private final Iterator<Project> projects;
- private ProjectMeasuresIndexerIterator(PreparedStatement measuresStatement, Map<Long, String> metricKeysByIds, List<Project> projects) {
+ private ProjectMeasuresIndexerIterator(PreparedStatement measuresStatement, List<Project> projects) {
this.measuresStatement = measuresStatement;
- this.metricKeysByIds = metricKeysByIds;
this.projects = projects.iterator();
}
public static ProjectMeasuresIndexerIterator create(DbSession session, @Nullable String projectUuid) {
try {
- Map<Long, String> metrics = selectMetricKeysByIds(session);
List<Project> projects = selectProjects(session, projectUuid);
- PreparedStatement projectsStatement = createMeasuresStatement(session, metrics.keySet());
- return new ProjectMeasuresIndexerIterator(projectsStatement, metrics, projects);
+ PreparedStatement projectsStatement = createMeasuresStatement(session);
+ return new ProjectMeasuresIndexerIterator(projectsStatement, projects);
} catch (SQLException e) {
throw new IllegalStateException("Fail to execute request to select all project measures", e);
}
}
- private static Map<Long, String> selectMetricKeysByIds(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.setString(1, NCLOC_LANGUAGE_DISTRIBUTION_KEY);
- stmt.setBoolean(2, true);
- return stmt;
- }
-
private static List<Project> selectProjects(DbSession session, @Nullable String projectUuid) {
List<Project> projects = new ArrayList<>();
try (PreparedStatement stmt = createProjectsStatement(session, projectUuid);
@@ -144,9 +129,11 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
private static PreparedStatement createProjectsStatement(DbSession session, @Nullable String projectUuid) {
try {
- String sql = SQL_PROJECTS;
- sql += projectUuid == null ? "" : PROJECT_FILTER;
- PreparedStatement stmt = session.getConnection().prepareStatement(sql);
+ StringBuilder sql = new StringBuilder(SQL_PROJECTS);
+ if (projectUuid != null) {
+ sql.append(PROJECT_FILTER);
+ }
+ PreparedStatement stmt = session.getConnection().prepareStatement(sql.toString());
stmt.setBoolean(1, true);
stmt.setBoolean(2, true);
stmt.setString(3, Scopes.PROJECT);
@@ -160,16 +147,11 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
}
}
- private static PreparedStatement createMeasuresStatement(DbSession session, Set<Long> metricIds) throws SQLException {
+ private static PreparedStatement createMeasuresStatement(DbSession session) throws SQLException {
try {
- String sql = StringUtils.replace(SQL_MEASURES, "{metricIds}", repeatCondition("?", metricIds.size(), ","));
- PreparedStatement stmt = session.getConnection().prepareStatement(sql);
- int index = 3;
- for (Long metricId : metricIds) {
- stmt.setLong(index, metricId);
- index++;
- }
- return stmt;
+ String metricNameQuestionMarks = METRIC_KEYS.stream().map(x -> "?").collect(Collectors.joining(","));
+ String sql = StringUtils.replace(SQL_MEASURES, "{metricNames}", metricNameQuestionMarks);
+ return session.getConnection().prepareStatement(sql);
} catch (SQLException e) {
throw new IllegalStateException("Fail to prepare SQL request to select measures", e);
}
@@ -188,13 +170,16 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
private Measures selectMeasures(String projectUuid, @Nullable String analysisUuid) {
Measures measures = new Measures();
- if (analysisUuid == null || metricKeysByIds.isEmpty()) {
+ if (analysisUuid == null) {
return measures;
}
ResultSet rs = null;
try {
- measuresStatement.setString(1, projectUuid);
- measuresStatement.setString(2, analysisUuid);
+ AtomicInteger index = new AtomicInteger(1);
+ measuresStatement.setString(index.getAndIncrement(), projectUuid);
+ measuresStatement.setString(index.getAndIncrement(), analysisUuid);
+ METRIC_KEYS.forEach(DatabaseUtils.setStrings(measuresStatement, index::getAndIncrement));
+ measuresStatement.setBoolean(index.getAndIncrement(), ENABLED);
rs = measuresStatement.executeQuery();
while (rs.next()) {
readMeasure(rs, measures);
@@ -207,9 +192,9 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
}
}
- private void readMeasure(ResultSet rs, Measures measures) throws SQLException {
- String metricKey = metricKeysByIds.get(rs.getLong(1));
- Optional<Double> value = metricKey.startsWith("new_") ? getDouble(rs, 3) : getDouble(rs, 2);
+ private static void readMeasure(ResultSet rs, Measures measures) throws SQLException {
+ String metricKey = rs.getString(FIELD_METRIC_NAME);
+ Optional<Double> value = metricKey.startsWith("new_") ? getDouble(rs, FIELD_MEASURE_VARIATION_VALUE_1) : getDouble(rs, FIELD_MEASURE_VALUE);
if (value.isPresent()) {
measures.addNumericMeasure(metricKey, value.get());
return;
@@ -225,7 +210,7 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
}
private static void readTextValue(ResultSet rs, Consumer<String> action) throws SQLException {
- String textValue = rs.getString(4);
+ String textValue = rs.getString(FIELD_MEASURE_TEXT_VALUE);
if (!rs.wasNull()) {
action.accept(textValue);
}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java
index 07b6da72a05..38ce4aa72a8 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java
@@ -162,7 +162,7 @@ public class ProjectMeasuresIndexerIteratorTest {
@Test
public void ignore_measure_that_does_not_have_value() throws Exception {
- MetricDto metric1 = insertIntMetric("lines");
+ MetricDto metric1 = insertIntMetric("coverage");
MetricDto metric2 = insertIntMetric("ncloc");
MetricDto leakMetric = insertIntMetric("new_lines");
ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.getDefaultOrganization());
@@ -173,12 +173,12 @@ public class ProjectMeasuresIndexerIteratorTest {
MeasureDto withoutValue = insertMeasure(project, analysis, metric2, null, null);
Map<String, Double> numericMeasures = createResultSetAndReturnDocsById().get(project.uuid()).getMeasures().getNumericMeasures();
- assertThat(numericMeasures).containsOnly(entry("lines", 10d), entry("new_lines", 20d));
+ assertThat(numericMeasures).containsOnly(entry("coverage", 10d), entry("new_lines", 20d));
}
@Test
public void ignore_numeric_measure_that_has_text_value_but_not_numeric_value() throws Exception {
- MetricDto metric1 = insertIntMetric("lines");
+ MetricDto metric1 = insertIntMetric("coverage");
MetricDto metric2 = insertIntMetric("ncloc");
ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.getDefaultOrganization());
SnapshotDto analysis = dbTester.components().insertProjectAndSnapshot(project);
@@ -187,7 +187,7 @@ public class ProjectMeasuresIndexerIteratorTest {
MeasureDto withTextValue = insertMeasure(project, analysis, metric2, "foo");
Map<String, Double> numericMeasures = createResultSetAndReturnDocsById().get(project.uuid()).getMeasures().getNumericMeasures();
- assertThat(numericMeasures).containsOnly(entry("lines", 10d));
+ assertThat(numericMeasures).containsOnly(entry("coverage", 10d));
}
@Test
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java
index a9a4edf5f58..684184527f5 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java
@@ -33,7 +33,6 @@ public class ComponentsWsModule extends Module {
SuggestionsAction.class,
TreeAction.class,
ShowAction.class,
- SearchProjectsAction.class,
- ProjectMeasuresQueryValidator.class);
+ SearchProjectsAction.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidator.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidator.java
index 7a45bf4d3e6..34cd4531054 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidator.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidator.java
@@ -19,73 +19,48 @@
*/
package org.sonar.server.component.ws;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
-import java.util.TreeSet;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.metric.MetricDto;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
import org.sonar.server.measure.index.ProjectMeasuresQuery;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Arrays.asList;
import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.db.measure.ProjectMeasuresIndexerIterator.METRIC_KEYS;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
public class ProjectMeasuresQueryValidator {
- private static final Set<String> NON_METRIC_SORT_KEYS = new HashSet<>(asList(SORT_BY_NAME, SORT_BY_LAST_ANALYSIS_DATE));
+ static final Set<String> NON_METRIC_SORT_KEYS = new HashSet<>(asList(SORT_BY_NAME, SORT_BY_LAST_ANALYSIS_DATE));
- private final DbClient dbClient;
-
- public ProjectMeasuresQueryValidator(DbClient dbClient) {
- this.dbClient = dbClient;
+ private ProjectMeasuresQueryValidator() {
}
- public void validate(DbSession dbSession, ProjectMeasuresQuery query) {
- Set<String> metricKeys = getMetrics(query);
- if (metricKeys.isEmpty()) {
- return;
- }
- List<MetricDto> dbMetrics = dbClient.metricDao().selectByKeys(dbSession, new ArrayList<>(metricKeys));
- checkMetricKeysExists(dbMetrics, metricKeys);
- checkMetricsAreEnabled(dbMetrics);
- checkMetricsAreNumerics(dbMetrics);
+ public static void validate(ProjectMeasuresQuery query) {
+ validateFilterKeys(query.getMetricCriteria().stream().map(MetricCriterion::getMetricKey).collect(toHashSet()));
+ validateSort(query.getSort());
}
- private static Set<String> getMetrics(ProjectMeasuresQuery query) {
- Set<String> metricKeys = query.getMetricCriteria().stream().map(MetricCriterion::getMetricKey).collect(toHashSet());
- if (query.getSort() != null && !NON_METRIC_SORT_KEYS.contains(query.getSort())) {
- metricKeys.add(query.getSort());
- }
- return metricKeys;
+ private static void validateFilterKeys(Set<String> metricsKeys) {
+ String invalidKeys = metricsKeys.stream()
+ .filter(metric -> !METRIC_KEYS.contains(metric))
+ .map(metric -> '\''+metric+'\'')
+ .collect(Collectors.joining(", "));
+ checkArgument(invalidKeys.isEmpty(), "Following metrics are not supported: %s", invalidKeys);
}
- private static void checkMetricKeysExists(List<MetricDto> dbMetrics, Set<String> inputMetricKeys) {
- Set<String> dbMetricsKeys = dbMetrics.stream().map(MetricDto::getKey).collect(toSet());
- Set<String> unknownKeys = inputMetricKeys.stream().filter(metricKey -> !dbMetricsKeys.contains(metricKey)).collect(toSet());
- checkArgument(unknownKeys.isEmpty(), "Unknown metric(s) %s", new TreeSet<>(unknownKeys));
- }
-
- private static void checkMetricsAreEnabled(List<MetricDto> dbMetrics) {
- Set<String> invalidKeys = dbMetrics.stream()
- .filter(metricDto -> !metricDto.isEnabled())
- .map(MetricDto::getKey)
- .collect(toSet());
- checkArgument(invalidKeys.isEmpty(), "Following metrics are disabled : %s", new TreeSet<>(invalidKeys));
- }
-
- private static void checkMetricsAreNumerics(List<MetricDto> dbMetrics) {
- Set<String> invalidKeys = dbMetrics.stream()
- .filter(MetricDto::isDataType)
- .map(MetricDto::getKey)
- .collect(toSet());
- checkArgument(invalidKeys.isEmpty(), "Following metrics are not numeric : %s", new TreeSet<>(invalidKeys));
+ private static void validateSort(@Nullable String sort) {
+ if (sort == null) {
+ return;
+ }
+ if (NON_METRIC_SORT_KEYS.contains(sort)) {
+ return;
+ }
+ validateFilterKeys(Collections.singleton(sort));
}
-
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
index 6d515dc7074..f941474f968 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
@@ -30,6 +30,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collector;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.api.resources.Qualifiers;
@@ -64,13 +65,14 @@ import static com.google.common.collect.Sets.newHashSet;
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.server.ws.WebService.Param.FIELDS;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.db.measure.ProjectMeasuresIndexerIterator.METRIC_KEYS;
import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.IS_FAVORITE_CRITERION;
import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
+import static org.sonar.server.component.ws.ProjectMeasuresQueryValidator.NON_METRIC_SORT_KEYS;
import static org.sonar.server.measure.index.ProjectMeasuresIndex.SUPPORTED_FACETS;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
@@ -92,13 +94,11 @@ public class SearchProjectsAction implements ComponentsWsAction {
private final DbClient dbClient;
private final ProjectMeasuresIndex index;
- private final ProjectMeasuresQueryValidator queryValidator;
private final UserSession userSession;
- public SearchProjectsAction(DbClient dbClient, ProjectMeasuresIndex index, ProjectMeasuresQueryValidator queryValidator, UserSession userSession) {
+ public SearchProjectsAction(DbClient dbClient, ProjectMeasuresIndex index, UserSession userSession) {
this.dbClient = dbClient;
this.index = index;
- this.queryValidator = queryValidator;
this.userSession = userSession;
}
@@ -128,7 +128,7 @@ public class SearchProjectsAction implements ComponentsWsAction {
.setSince("6.3");
action.createParam(Param.FACETS)
.setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.")
- .setPossibleValues(SUPPORTED_FACETS);
+ .setPossibleValues(SUPPORTED_FACETS.stream().sorted().collect(MoreCollectors.toList(SUPPORTED_FACETS.size())));
action
.createParam(PARAM_FILTER)
.setDescription("Filter of projects on name, key, measure value, quality gate, language, tag or whether a project is a favorite or not.<br>" +
@@ -144,8 +144,11 @@ public class SearchProjectsAction implements ComponentsWsAction {
"</ul>" +
"To filter on project name or key, use the 'query' keyword, for instance : <code>filter='query = \"Sonar\"'</code>.<br>" +
"<br>" +
- "To filter on any numeric metric, provide the metric key.<br>" +
- "Use the WS api/metrics/search to find the key of a metric.<br>" +
+ "To filter on a numeric metric, provide the metric key.<br>" +
+ "These are the supported metric keys:<br>" +
+ "<ul>" +
+ METRIC_KEYS.stream().sorted().map(key -> "<li>" + key + "</li>").collect(Collectors.joining()) +
+ "</ul>" +
"<br>" +
"To filter on a rating, provide the corresponding metric key (ex: reliability_rating for reliability rating).<br>" +
"The possible values are:" +
@@ -175,10 +178,11 @@ public class SearchProjectsAction implements ComponentsWsAction {
" <li>to filter on several tags you must use <code>tag in (offshore, java)</code></li>" +
"</ul>");
action.createParam(Param.SORT)
- .setDescription("Sort projects by numeric metric key, quality gate status (using '%s'), last analysis date (using '%s'), or by project name.<br/>" +
- "See '%s' parameter description for the possible metric values", ALERT_STATUS_KEY, SORT_BY_LAST_ANALYSIS_DATE, PARAM_FILTER)
+ .setDescription("Sort projects by numeric metric key, quality gate status (using '%s'), last analysis date (using '%s'), or by project name.",
+ ALERT_STATUS_KEY, SORT_BY_LAST_ANALYSIS_DATE, PARAM_FILTER)
.setDefaultValue(SORT_BY_NAME)
- .setExampleValue(NCLOC_KEY)
+ .setPossibleValues(
+ Stream.concat(METRIC_KEYS.stream(), NON_METRIC_SORT_KEYS.stream()).sorted().collect(MoreCollectors.toList(METRIC_KEYS.size() + NON_METRIC_SORT_KEYS.size())))
.setSince("6.4");
action.createParam(Param.ASCENDING)
.setDescription("Ascending sort")
@@ -231,7 +235,7 @@ public class SearchProjectsAction implements ComponentsWsAction {
.map(OrganizationDto::getUuid)
.ifPresent(query::setOrganizationUuid);
- queryValidator.validate(dbSession, query);
+ ProjectMeasuresQueryValidator.validate(query);
SearchIdResult<String> esResults = index.search(query, new SearchOptions()
.addFacets(request.getFacets())
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java
index 6c965f414e7..342064d80bd 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java
@@ -30,6 +30,6 @@ public class ComponentsWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new ComponentsWsModule().configure(container);
- assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 9);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 8);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java
index 9220c27e19a..19e10ab1b0c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java
@@ -19,142 +19,234 @@
*/
package org.sonar.server.component.ws;
+import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.metric.MetricDto;
import org.sonar.server.measure.index.ProjectMeasuresQuery;
-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.api.measures.Metric.ValueType.STRING;
-import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
-import static org.sonar.db.metric.MetricTesting.newMetricDto;
-import static org.sonar.server.component.ws.FilterParser.Operator.EQ;
-import static org.sonar.server.component.ws.FilterParser.Operator.GT;
-import static org.sonar.server.component.ws.FilterParser.Operator.LT;
-import static org.sonar.server.component.ws.FilterParser.Operator.LTE;
-import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion.create;
public class ProjectMeasuresQueryValidatorTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
- @Rule
- public DbTester db = DbTester.create(System2.INSTANCE);
+ @Test
+ public void query_with_empty_metrics_is_valid() throws Exception {
+ ProjectMeasuresQueryValidator.validate(new ProjectMeasuresQuery());
+ }
- private DbClient dbClient = db.getDbClient();
- private DbSession dbSession = db.getSession();
+ @Test
+ public void filter_by_ncloc_is_valid() throws Exception {
+ assertValidFilterKey("ncloc");
+ }
- private ProjectMeasuresQueryValidator underTest = new ProjectMeasuresQueryValidator(dbClient);
+ @Test
+ public void filter_by_duplicated_lines_density_is_valid() throws Exception {
+ assertValidFilterKey("duplicated_lines_density");
+ }
@Test
- public void query_with_empty_metrics_is_valid() throws Exception {
- underTest.validate(dbSession, new ProjectMeasuresQuery());
+ public void filter_by_coverage_is_valid() throws Exception {
+ assertValidFilterKey("coverage");
}
@Test
- public void does_not_fail_when_metric_criteria_contains_an_existing_metric() throws Exception {
- insertValidMetric("ncloc");
- ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create("ncloc", GT, 10d));
+ public void filter_by_sqale_rating_is_valid() throws Exception {
+ assertValidFilterKey("sqale_rating");
+ }
- underTest.validate(dbSession, query);
+ @Test
+ public void filter_by_reliability_rating_is_valid() throws Exception {
+ assertValidFilterKey("reliability_rating");
}
@Test
- public void does_not_fail_when_sort_is_by_name() throws Exception {
- insertValidMetric("ncloc");
- ProjectMeasuresQuery query = new ProjectMeasuresQuery()
- .addMetricCriterion(MetricCriterion.create("ncloc", GT, 10d))
- .setSort("name");
+ public void filter_by_security_rating_is_valid() throws Exception {
+ assertValidFilterKey("security_rating");
+ }
- underTest.validate(dbSession, query);
+ @Test
+ public void filter_by_alert_status_is_valid() throws Exception {
+ assertValidFilterKey("alert_status");
}
@Test
- public void does_not_fail_when_sort_contains_an_existing_metric() throws Exception {
- insertValidMetric("ncloc");
- insertValidMetric("debt");
- ProjectMeasuresQuery query = new ProjectMeasuresQuery()
- .addMetricCriterion(MetricCriterion.create("ncloc", GT, 10d))
- .setSort("debt");
+ public void filter_by_ncloc_language_distribution_is_valid() throws Exception {
+ assertValidFilterKey("ncloc_language_distribution");
+ }
- underTest.validate(dbSession, query);
+ @Test
+ public void filter_by_new_security_rating_is_valid() throws Exception {
+ assertValidFilterKey("new_security_rating");
}
@Test
- public void fail_when_metric_are_not_numeric() throws Exception {
- insertMetric(createValidMetric("ncloc").setValueType(INT.name()));
- insertMetric(createValidMetric("debt").setValueType(WORK_DUR.name()));
- insertMetric(createValidMetric("data").setValueType(DATA.name()));
- insertMetric(createValidMetric("distrib").setValueType(DISTRIB.name()));
- insertMetric(createValidMetric("string").setValueType(STRING.name()));
- ProjectMeasuresQuery query = new ProjectMeasuresQuery()
- .addMetricCriterion(MetricCriterion.create("data", GT, 10d))
- .addMetricCriterion(MetricCriterion.create("distrib", EQ, 11d))
- .addMetricCriterion(MetricCriterion.create("ncloc", LTE, 20d))
- .addMetricCriterion(MetricCriterion.create("debt", LT, 20d))
- .addMetricCriterion(MetricCriterion.create("string", EQ, 40d));
+ public void filter_by_new_maintainability_rating_is_valid() throws Exception {
+ assertValidFilterKey("new_maintainability_rating");
+ }
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Following metrics are not numeric : [data, distrib, string]");
- underTest.validate(dbSession, query);
+ @Test
+ public void filter_by_new_coverage_is_valid() throws Exception {
+ assertValidFilterKey("new_coverage");
}
@Test
- public void fail_when_metric_is_disabled() throws Exception {
- insertMetric(createValidMetric("ncloc").setEnabled(false));
- insertMetric(createValidMetric("debt").setEnabled(false));
- ProjectMeasuresQuery query = new ProjectMeasuresQuery()
- .addMetricCriterion(MetricCriterion.create("ncloc", GT, 10d))
- .setSort("debt");
+ public void filter_by_new_duplicated_lines_density_is_valid() throws Exception {
+ assertValidFilterKey("new_duplicated_lines_density");
+ }
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Following metrics are disabled : [debt, ncloc]");
- underTest.validate(dbSession, query);
+ @Test
+ public void filter_by_new_lines_is_valid() throws Exception {
+ assertValidFilterKey("new_lines");
}
@Test
- public void fail_when_metric_does_not_exists() throws Exception {
- insertValidMetric("ncloc");
- ProjectMeasuresQuery query = new ProjectMeasuresQuery()
- .addMetricCriterion(MetricCriterion.create("unknown", GT, 10d))
- .setSort("debt");
+ public void filter_by_new_reliability_rating_is_valid() throws Exception {
+ assertValidFilterKey("new_reliability_rating");
+ }
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Unknown metric(s) [debt, unknown]");
- underTest.validate(dbSession, query);
+ @Test
+ public void filter_by_bla_is_invalid() throws Exception {
+ assertInvalidFilterKey("bla");
+ }
+
+ @Test
+ public void filter_by_bla_and_new_lines_is_invalid() throws Exception {
+ assertInvalidFilterKeys("Following metrics are not supported: 'bla'", "bla", "new_lines");
+ }
+
+ @Test
+ public void filter_by_new_lines_and_bla_is_invalid() throws Exception {
+ assertInvalidFilterKeys("Following metrics are not supported: 'bla'", "new_lines", "bla");
+ }
+
+ @Test
+ public void filter_by_NeW_LiNeS_is_invalid() throws Exception {
+ assertInvalidFilterKey("NeW_LiNeS");
}
@Test
- public void return_all_unknown_metrics() throws Exception {
- insertValidMetric("ncloc");
- ProjectMeasuresQuery query = new ProjectMeasuresQuery()
- .addMetricCriterion(MetricCriterion.create("debt", GT, 10d))
- .addMetricCriterion(MetricCriterion.create("ncloc", LTE, 20d))
- .addMetricCriterion(MetricCriterion.create("coverage", GT, 30d))
- .setSort("duplications");
+ public void filter_by_empty_string_is_invalid() throws Exception {
+ assertInvalidFilterKey("");
+ }
+
+ @Test
+ public void sort_by_ncloc_is_valid() throws Exception {
+ assertValidSortKey("ncloc");
+ }
+
+ @Test
+ public void sort_by_duplicated_lines_density_is_valid() throws Exception {
+ assertValidSortKey("duplicated_lines_density");
+ }
+
+ @Test
+ public void sort_by_coverage_is_valid() throws Exception {
+ assertValidSortKey("coverage");
+ }
+
+ @Test
+ public void sort_by_sqale_rating_is_valid() throws Exception {
+ assertValidSortKey("sqale_rating");
+ }
+ @Test
+ public void sort_by_reliability_rating_is_valid() throws Exception {
+ assertValidSortKey("reliability_rating");
+ }
+
+ @Test
+ public void sort_by_security_rating_is_valid() throws Exception {
+ assertValidSortKey("security_rating");
+ }
+
+ @Test
+ public void sort_by_alert_status_is_valid() throws Exception {
+ assertValidSortKey("alert_status");
+ }
+
+ @Test
+ public void sort_by_ncloc_language_distribution_is_valid() throws Exception {
+ assertValidSortKey("ncloc_language_distribution");
+ }
+
+ @Test
+ public void sort_by_new_security_rating_is_valid() throws Exception {
+ assertValidSortKey("new_security_rating");
+ }
+
+ @Test
+ public void sort_by_new_maintainability_rating_is_valid() throws Exception {
+ assertValidSortKey("new_maintainability_rating");
+ }
+
+ @Test
+ public void sort_by_new_coverage_is_valid() throws Exception {
+ assertValidSortKey("new_coverage");
+ }
+
+ @Test
+ public void sort_by_new_duplicated_lines_density_is_valid() throws Exception {
+ assertValidSortKey("new_duplicated_lines_density");
+ }
+
+ @Test
+ public void sort_by_new_lines_is_valid() throws Exception {
+ assertValidSortKey("new_lines");
+ }
+
+ @Test
+ public void sort_by_new_reliability_rating_is_valid() throws Exception {
+ assertValidSortKey("new_reliability_rating");
+ }
+
+ @Test
+ public void sort_by_bla_is_invalid() throws Exception {
+ assertInvalidSortKey("bla");
+ }
+
+ @Test
+ public void sort_by_NeW_lInEs_is_invalid() throws Exception {
+ assertInvalidSortKey("NeW_lInEs");
+ }
+
+ @Test
+ public void sort_by_empty_string_is_invalid() throws Exception {
+ assertInvalidSortKey("");
+ }
+
+ private void assertValidSortKey(String metricKey) {
+ // do not expect an exception
+ ProjectMeasuresQueryValidator.validate(new ProjectMeasuresQuery().setSort(metricKey));
+ }
+
+ private void assertInvalidSortKey(String metricKey) {
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Unknown metric(s) [coverage, debt, duplications]");
- underTest.validate(dbSession, query);
+ expectedException.expectMessage("Following metrics are not supported: '" + metricKey + "'");
+
+ ProjectMeasuresQueryValidator.validate(new ProjectMeasuresQuery().setSort(metricKey));
}
- private void insertValidMetric(String metricKey) {
- insertMetric(createValidMetric(metricKey));
+ private static void assertValidFilterKey(String... metricKeys) {
+ // do not expect an exception
+ validateFilterKeys(metricKeys);
}
- private void insertMetric(MetricDto metricDto) {
- dbClient.metricDao().insert(dbSession, metricDto);
+ private void assertInvalidFilterKey(String metricKey) {
+ assertInvalidFilterKeys("Following metrics are not supported: '" + metricKey + "'", metricKey);
+ }
+
+ private void assertInvalidFilterKeys(String message, String... metricKeys) {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(message);
+ validateFilterKeys(metricKeys);
}
- private static MetricDto createValidMetric(String metricKey) {
- return newMetricDto().setKey(metricKey).setValueType(INT.name()).setEnabled(true).setHidden(false);
+ private static void validateFilterKeys(String... metricKeys) {
+ ProjectMeasuresQuery query = new ProjectMeasuresQuery();
+ Arrays.stream(metricKeys).forEachOrdered(metricKey -> query.addMetricCriterion(create(metricKey, FilterParser.Operator.LT, 80d)));
+ ProjectMeasuresQueryValidator.validate(query);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
index 59bb204ac1c..18608c01696 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
@@ -131,10 +131,9 @@ public class SearchProjectsActionTest {
private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, new ProjectMeasuresIndexer(dbClient, es.client()));
private ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new AuthorizationTypeSupport(userSession));
private ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
- private ProjectMeasuresQueryValidator queryValidator = new ProjectMeasuresQueryValidator(dbClient);
private WsActionTester ws = new WsActionTester(
- new SearchProjectsAction(dbClient, index, queryValidator, userSession));
+ new SearchProjectsAction(dbClient, index, userSession));
private SearchProjectsRequest.Builder request = SearchProjectsRequest.builder();
@@ -162,8 +161,22 @@ public class SearchProjectsActionTest {
Param sort = def.param("s");
assertThat(sort.defaultValue()).isEqualTo("name");
- assertThat(sort.exampleValue()).isEqualTo("ncloc");
- assertThat(sort.possibleValues()).isNull();
+ assertThat(sort.possibleValues()).containsExactlyInAnyOrder("coverage",
+ "reliability_rating",
+ "duplicated_lines_density",
+ "ncloc_language_distribution",
+ "new_lines",
+ "security_rating",
+ "new_reliability_rating",
+ "new_coverage",
+ "new_security_rating",
+ "sqale_rating",
+ "new_duplicated_lines_density",
+ "alert_status",
+ "ncloc",
+ "new_maintainability_rating",
+ "name",
+ "analysisDate");
Param asc = def.param("asc");
assertThat(asc.defaultValue()).isEqualTo("true");
@@ -1047,12 +1060,23 @@ public class SearchProjectsActionTest {
}
@Test
- public void fail_when_metrics_are_unknown() {
+ public void fail_when_filter_metrics_are_unknown() {
userSession.logIn();
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Unknown metric(s) [coverage, debt]");
+ expectedException.expectMessage("Following metrics are not supported: 'debt'");
- request.setFilter("coverage > 80").setSort("debt");
+ request.setFilter("debt > 80");
+
+ call(request);
+ }
+
+ @Test
+ public void fail_when_sort_metrics_are_unknown() {
+ userSession.logIn();
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of parameter 's' (debt) must be one of: [");
+
+ request.setSort("debt");
call(request);
}