diff options
author | Daniel Schwarz <daniel.schwarz@sonarsource.com> | 2017-05-24 12:46:26 +0200 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-06-09 08:26:48 +0200 |
commit | 884e34739f55e058819001936945e89b41035ffa (patch) | |
tree | c57f8840b71ebdba5fa0bd783eb37e900870f205 | |
parent | 2fb2871f4be5ea216ab71e67b80622c040fd1791 (diff) | |
download | sonarqube-884e34739f55e058819001936945e89b41035ffa.tar.gz sonarqube-884e34739f55e058819001936945e89b41035ffa.zip |
SONAR-9230 only index project measures, that will actually be used
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); } |