import org.sonarqube.ws.WsComponents.SearchProjectsWsResponse;
import org.sonarqube.ws.client.component.SearchProjectsRequest;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.build;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
}
private SearchResults searchProjects(DbSession dbSession, SearchProjectsRequest request) {
- String filter = request.getFilter();
- if (filter != null) {
- SearchProjectsCriteriaQuery query = build(filter);
- searchProjectsQueryBuilderValidator.validate(dbSession, query);
- }
- SearchIdResult<String> searchResult = index.search(new SearchOptions().setPage(request.getPage(), request.getPageSize()));
+ String filter = firstNonNull(request.getFilter(), "");
+ SearchProjectsCriteriaQuery query = build(filter);
+ searchProjectsQueryBuilderValidator.validate(dbSession, query);
+
+ SearchIdResult<String> searchResult = index.search(query, new SearchOptions().setPage(request.getPage(), request.getPageSize()));
Ordering<ComponentDto> ordering = Ordering.explicit(searchResult.getIds()).onResultOf(ComponentDto::uuid);
List<ComponentDto> projects = ordering.immutableSortedCopy(dbClient.componentDao().selectByUuids(dbSession, searchResult.getIds()));
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang.StringUtils;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
}
public static SearchProjectsCriteriaQuery build(String filter) {
+ if (StringUtils.isBlank(filter)) {
+ return new SearchProjectsCriteriaQuery();
+ }
+
SearchProjectsCriteriaQuery query = new SearchProjectsCriteriaQuery();
+
CRITERIA_SPLITTER.split(filter.toLowerCase(ENGLISH))
.forEach(criteria -> processCriteria(criteria, query));
return query;
public static class SearchProjectsCriteriaQuery {
public enum Operator {
- LT("<="), GT(">"), EQ("=");
+ LTE("<="), GT(">"), EQ("=");
String value;
private List<MetricCriteria> metricCriterias = new ArrayList<>();
- SearchProjectsCriteriaQuery addMetricCriteria(MetricCriteria metricCriteria) {
+ public SearchProjectsCriteriaQuery addMetricCriteria(MetricCriteria metricCriteria) {
metricCriterias.add(metricCriteria);
return this;
}
}
public static class MetricCriteria {
- private String metricKey;
- private Operator operator;
- private double value;
+ private final String metricKey;
+ private final Operator operator;
+ private final double value;
- private MetricCriteria(String metricKey, Operator operator, double value) {
+ public MetricCriteria(String metricKey, Operator operator, double value) {
this.metricKey = metricKey;
this.operator = operator;
this.value = value;
*/
package org.sonar.server.project.es;
+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.server.es.BaseDoc;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
+
public class ProjectMeasuresDoc extends BaseDoc {
public ProjectMeasuresDoc() {
setField(ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT, d);
return this;
}
+
+ public Collection<Map<String, Object>> getMeasures() {
+ return getField(FIELD_MEASURES);
+ }
+
+ public ProjectMeasuresDoc setMeasures(Collection<Map<String, Object>> measures) {
+ setField(FIELD_MEASURES, measures);
+ return this;
+ }
}
package org.sonar.server.project.es;
import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortOrder;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.MetricCriteria;
import org.sonar.server.es.BaseIndex;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.SearchIdResult;
import org.sonar.server.es.SearchOptions;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
+import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE;
import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_NAME;
import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
public class ProjectMeasuresIndex extends BaseIndex {
+ private static final String FIELD_KEY = FIELD_MEASURES + "." + FIELD_MEASURES_KEY;
+ private static final String FIELD_VALUE = FIELD_MEASURES + "." + FIELD_MEASURES_VALUE;
+
public ProjectMeasuresIndex(EsClient client) {
super(client);
}
- public SearchIdResult<String> search(SearchOptions searchOptions) {
- QueryBuilder condition = QueryBuilders.matchAllQuery();
+ public SearchIdResult<String> search(SearchProjectsCriteriaQuery query, SearchOptions searchOptions) {
+ BoolQueryBuilder metricFilters = boolQuery();
+ query.getMetricCriterias().stream()
+ .map(criteria -> nestedQuery(FIELD_MEASURES, boolQuery()
+ .filter(termQuery(FIELD_KEY, criteria.getMetricKey()))
+ .filter(toValueQuery(criteria))))
+ .forEach(metricFilters::filter);
+ QueryBuilder esQuery = query.getMetricCriterias().isEmpty() ? matchAllQuery() : metricFilters;
SearchRequestBuilder request = getClient()
.prepareSearch(INDEX_PROJECT_MEASURES)
.setTypes(TYPE_PROJECT_MEASURES)
.setFetchSource(false)
- .setQuery(condition)
+ .setQuery(esQuery)
.setFrom(searchOptions.getOffset())
.setSize(searchOptions.getLimit())
.addSort(FIELD_NAME + "." + SORT_SUFFIX, SortOrder.ASC);
return new SearchIdResult<>(request.get(), id -> id);
}
+
+ private static QueryBuilder toValueQuery(MetricCriteria criteria) {
+ String fieldName = FIELD_VALUE;
+
+ switch (criteria.getOperator()) {
+ case EQ:
+ return termQuery(fieldName, criteria.getValue());
+ case GT:
+ return rangeQuery(fieldName).gt(criteria.getValue());
+ case LTE:
+ return rangeQuery(fieldName).lte(criteria.getValue());
+ default:
+ throw new IllegalStateException("Metric criteria non supported: " + criteria.getOperator().name());
+ }
+
+ }
}
public static final String FIELD_KEY = "key";
public static final String FIELD_NAME = "name";
public static final String FIELD_ANALYSED_AT = "analysedAt";
+ public static final String FIELD_MEASURES = "measures";
+ public static final String FIELD_MEASURES_KEY = "key";
+ public static final String FIELD_MEASURES_VALUE = "value";
private final Settings settings;
mapping.stringFieldBuilder(FIELD_KEY).disableNorms().build();
mapping.stringFieldBuilder(FIELD_NAME).enableSorting().enableGramSearch().build();
mapping.createDateTimeField(FIELD_ANALYSED_AT);
+ mapping.nestedFieldBuilder(FIELD_MEASURES)
+ .addStringFied(FIELD_MEASURES_KEY)
+ .addStringFied(FIELD_MEASURES_VALUE)
+ .build();
+
// do not store document but only indexation of information
mapping.setEnableSource(false);
}
.extracting(MetricCriteria::getMetricKey, MetricCriteria::getOperator, MetricCriteria::getValue)
.containsOnly(
tuple("ncloc", Operator.GT, 10d),
- tuple("coverage", Operator.LT, 80d));
+ tuple("coverage", Operator.LTE, 80d));
}
@Test
.extracting(MetricCriteria::getMetricKey, MetricCriteria::getOperator, MetricCriteria::getValue)
.containsOnly(
tuple("ncloc", Operator.GT, 10d),
- tuple("coverage", Operator.LT, 80d));
+ tuple("coverage", Operator.LTE, 80d));
}
@Test
.containsOnly(tuple("ncloc", Operator.GT, 10d));
}
+ @Test
+ public void accept_empty_query() throws Exception {
+ SearchProjectsCriteriaQuery result = build("");
+
+ assertThat(result.getMetricCriterias()).isEmpty();
+ }
+
@Test
public void fail_on_unknown_operator() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Invalid criteria 'ncloc >='");
build("ncloc >=");
}
-
- @Test
- public void fail_when_no_criteria_provided() throws Exception {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Invalid criteria ''");
- build("");
- }
}
package org.sonar.server.project.es;
import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
import java.util.List;
+import java.util.Map;
import java.util.stream.IntStream;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.config.MapSettings;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.MetricCriteria;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.Operator;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.SearchIdResult;
import org.sonar.server.es.SearchOptions;
+import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
public class ProjectMeasuresIndexTest {
+ private static final String COVERAGE = "coverage";
+ private static final String NCLOC = "ncloc";
@Rule
public EsTester es = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()));
private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client());
+ @Test
+ public void empty_search() {
+ List<String> result = underTest.search(new SearchProjectsCriteriaQuery(), new SearchOptions()).getIds();
+
+ assertThat(result).isEmpty();
+ }
+
@Test
public void search_sort_by_name_case_insensitive() {
addDocs(newDoc("P1", "K1", "Windows"),
newDoc("P3", "K3", "apachee"),
newDoc("P2", "K2", "Apache"));
- List<String> result = underTest.search(new SearchOptions()).getIds();
+ List<String> result = underTest.search(new SearchProjectsCriteriaQuery(), new SearchOptions()).getIds();
assertThat(result).containsExactly("P2", "P3", "P1");
}
IntStream.rangeClosed(1, 9)
.forEach(i -> addDocs(newDoc("P" + i, "K" + i, "P" + i)));
- SearchIdResult<String> result = underTest.search(new SearchOptions().setPage(2, 3));
+ SearchIdResult<String> result = underTest.search(new SearchProjectsCriteriaQuery(), new SearchOptions().setPage(2, 3));
assertThat(result.getIds()).containsExactly("P4", "P5", "P6");
assertThat(result.getTotal()).isEqualTo(9);
}
- private static ProjectMeasuresDoc newDoc(String uuid, String key, String name) {
- return new ProjectMeasuresDoc()
- .setId(uuid)
- .setKey(key)
- .setName(name);
+ @Test
+ public void filter_with_lower_than_or_equals() {
+ addDocs(
+ newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 79d), newMeasure(NCLOC, 10_000d))),
+ newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d))),
+ newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 81d), newMeasure(NCLOC, 10_000d))));
+
+ SearchProjectsCriteriaQuery esQuery = new SearchProjectsCriteriaQuery()
+ .addMetricCriteria(new MetricCriteria(COVERAGE, Operator.LTE, 80d));
+ List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+ assertThat(result).containsExactly("P1", "P2");
+ }
+
+ @Test
+ public void filter_with_greater_than() {
+ addDocs(
+ newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d))),
+ newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_001d))),
+ newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_001d))));
+
+ SearchProjectsCriteriaQuery esQuery = new SearchProjectsCriteriaQuery()
+ .addMetricCriteria(new MetricCriteria(NCLOC, Operator.GT, 10_000d));
+ List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+ assertThat(result).containsExactly("P2", "P3");
+ }
+
+ @Test
+ public void filter_with_equals() {
+ addDocs(
+ newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 79d), newMeasure(NCLOC, 10_000d))),
+ newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d))),
+ newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 81d), newMeasure(NCLOC, 10_000d))));
+
+ SearchProjectsCriteriaQuery esQuery = new SearchProjectsCriteriaQuery()
+ .addMetricCriteria(new MetricCriteria(COVERAGE, Operator.EQ, 80d));
+ List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+ assertThat(result).containsExactly("P2");
+ }
+
+ @Test
+ public void filter_on_several_metrics() {
+ addDocs(
+ newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 81d), newMeasure(NCLOC, 10_001d))),
+ newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_001d))),
+ newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 79d), newMeasure(NCLOC, 10_000d))));
+
+ SearchProjectsCriteriaQuery esQuery = new SearchProjectsCriteriaQuery()
+ .addMetricCriteria(new MetricCriteria(COVERAGE, Operator.LTE, 80d))
+ .addMetricCriteria(new MetricCriteria(NCLOC, Operator.GT, 10_000d));
+ List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+ assertThat(result).containsExactly("P2");
}
private void addDocs(ProjectMeasuresDoc... docs) {
Throwables.propagate(e);
}
}
+
+ private static ProjectMeasuresDoc newDoc(String uuid, String key, String name) {
+ return new ProjectMeasuresDoc()
+ .setId(uuid)
+ .setKey(key)
+ .setName(name);
+ }
+
+ private Map<String, Object> newMeasure(String key, double value) {
+ return ImmutableMap.of("key", key, "value", value);
+ }
}