public class FilterParser {
+ private static final String DOUBLE_QUOTES = "\"";
+
private static final Splitter CRITERIA_SPLITTER = Splitter.on(Pattern.compile("and", Pattern.CASE_INSENSITIVE));
private static final Splitter IN_VALUES_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
- private static final Pattern PATTERN = Pattern.compile("(\\w+)\\s*([<>]?[=]?)\\s*(\\S*)", Pattern.CASE_INSENSITIVE);
+ private static final Pattern PATTERN = Pattern.compile("(\\w+)\\s*([<>]?[=]?)\\s*(.*)", Pattern.CASE_INSENSITIVE);
private static final Pattern PATTERN_HAVING_VALUES = Pattern.compile("(\\w+)\\s+(in)\\s+\\((.*)\\)", Pattern.CASE_INSENSITIVE);
+ private FilterParser(){
+ // Only static methods
+ }
+
public static List<Criterion> parse(String filter) {
return StreamSupport.stream(CRITERIA_SPLITTER.split(filter.trim()).spliterator(), false)
.filter(Objects::nonNull)
String value = matcher.group(3);
if (!isNullOrEmpty(operatorValue) && !isNullOrEmpty(value)) {
builder.setOperator(Operator.getByValue(operatorValue));
- builder.setValue(value);
+ builder.setValue(sanitizeValue(value));
}
return builder.build();
}
return builder.build();
}
+ @CheckForNull
+ private static String sanitizeValue(@Nullable String value) {
+ if (value == null) {
+ return null;
+ }
+ if (value.length() > 2 && value.startsWith(DOUBLE_QUOTES) && value.endsWith(DOUBLE_QUOTES)) {
+ return value.substring(1, value.length() - 1);
+ }
+ return value;
+ }
+
public static class Criterion {
private final String key;
private final Operator operator;
class ProjectMeasuresQueryFactory {
public static final String IS_FAVORITE_CRITERION = "isFavorite";
+ public static final String QUERY_KEY = "query";
private ProjectMeasuresQueryFactory() {
// prevent instantiation
return;
}
+ if (QUERY_KEY.equalsIgnoreCase(key)) {
+ processQuery(criterion, query);
+ return;
+ }
+
String value = criterion.getValue();
checkArgument(value != null, "Value cannot be null for '%s'", key);
if (ALERT_STATUS_KEY.equals(key)) {
List<String> values = criterion.getValues();
if (value != null && EQ.equals(operator)) {
query.setLanguages(singleton(value));
- } else if (!values.isEmpty() && IN.equals(operator)) {
+ return;
+ }
+ if (!values.isEmpty() && IN.equals(operator)) {
query.setLanguages(new HashSet<>(values));
- } else {
- throw new IllegalArgumentException("Language should be set either by using 'language = java' or 'language IN (java, js)'");
+ return;
}
+ throw new IllegalArgumentException("Language should be set either by using 'language = java' or 'language IN (java, js)'");
+ }
+
+ private static void processQuery(FilterParser.Criterion criterion, ProjectMeasuresQuery query) {
+ Operator operatorValue = criterion.getOperator();
+ String value = criterion.getValue();
+ checkArgument(value != null, "Query is invalid");
+ checkArgument(EQ.equals(operatorValue), "Query should only be used with equals operator");
+ query.setQueryText(value);
}
private static void processQualityGateStatus(FilterParser.Criterion criterion, ProjectMeasuresQuery query) {
}
}
-
}
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.stream.IntStream;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.sonar.server.es.SearchIdResult;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.es.StickyFacetBuilder;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
+import org.sonar.server.es.textsearch.ComponentTextSearchQueryFactory;
import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
import org.sonar.server.permission.index.AuthorizationTypeSupport;
.filter(toValueQuery(criterion))))
.forEach(metricFilters::must);
filters.put(entry.getKey(), metricFilters);
-
});
query.getQualityGateStatus()
query.getOrganizationUuid()
.ifPresent(organizationUuid -> filters.put(FIELD_ORGANIZATION_UUID, termQuery(FIELD_ORGANIZATION_UUID, organizationUuid)));
+
+ createTextQueryFilter(query).ifPresent(queryBuilder -> filters.put("textQuery", queryBuilder));
return filters;
}
+ private static Optional<QueryBuilder> createTextQueryFilter(ProjectMeasuresQuery query) {
+ Optional<String> queryText = query.getQueryText();
+ if (!queryText.isPresent()) {
+ return Optional.empty();
+ }
+ ComponentTextSearchQueryFactory.ComponentTextSearchQuery componentTextSearchQuery = ComponentTextSearchQueryFactory.ComponentTextSearchQuery.builder()
+ .setQueryText(queryText.get())
+ .setFieldKey(FIELD_KEY)
+ .setFieldName(FIELD_NAME)
+ .build();
+ return Optional.of(ComponentTextSearchQueryFactory.createQuery(componentTextSearchQuery, ComponentTextSearchFeature.values()));
+ }
+
private static QueryBuilder toValueQuery(MetricCriterion criterion) {
String fieldName = FIELD_MEASURES_VALUE;
import org.sonar.server.es.IndexDefinition;
import org.sonar.server.es.NewIndex;
+import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
public class ProjectMeasuresIndexDefinition implements IndexDefinition {
.requireProjectAuthorization();
mapping.stringFieldBuilder(FIELD_ORGANIZATION_UUID).build();
- mapping.stringFieldBuilder(FIELD_KEY).disableNorms().build();
- mapping.stringFieldBuilder(FIELD_NAME).addSubFields(SORTABLE_ANALYZER).build();
+ mapping.stringFieldBuilder(FIELD_KEY).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
+ mapping.stringFieldBuilder(FIELD_NAME).addSubFields(SORTABLE_ANALYZER, SEARCH_GRAMS_ANALYZER).build();
mapping.stringFieldBuilder(FIELD_QUALITY_GATE_STATUS).build();
mapping.createDateTimeField(FIELD_ANALYSED_AT);
mapping.nestedFieldBuilder(FIELD_MEASURES)
private Set<String> languages;
private String sort = SORT_BY_NAME;
private boolean asc = true;
+ private String queryText;
public ProjectMeasuresQuery addMetricCriterion(MetricCriterion metricCriterion) {
this.metricCriteria.add(metricCriterion);
return Optional.ofNullable(languages);
}
+ public Optional<String> getQueryText() {
+ return Optional.ofNullable(queryText);
+ }
+
+ public ProjectMeasuresQuery setQueryText(@Nullable String queryText) {
+ this.queryText = queryText;
+ return this;
+ }
+
public String getSort() {
return sort;
}
.containsOnly(
tuple("ncloc", GT, "10", emptyList()),
tuple("coverage", LTE, "80", emptyList()),
- tuple("language", IN, null, asList("java", "js")));
+ tuple("language", IN, null, asList("java", "js")));
}
@Test
tuple("language", IN, null, asList("java", "js")));
}
+ @Test
+ public void parse_filter_starting_and_ending_with_double_quotes() throws Exception {
+ assertThat(FilterParser.parse("q = \"Sonar Qube\""))
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "Sonar Qube"));
+
+ assertThat(FilterParser.parse("q = \"Sonar\"Qube\""))
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "Sonar\"Qube"));
+
+ assertThat(FilterParser.parse("q = Sonar\"Qube"))
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "Sonar\"Qube"));
+
+ assertThat(FilterParser.parse("q=\"Sonar Qube\""))
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "Sonar Qube"));
+ }
+
@Test
public void accept_empty_query() throws Exception {
List<Criterion> criterion = FilterParser.parse("");
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.IN;
+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.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.Level.OK;
emptySet());
}
+ @Test
+ public void create_query_having_q() throws Exception {
+ List<Criterion> criteria = singletonList(Criterion.builder().setKey("query").setOperator(EQ).setValue("Sonar Qube").build());
+
+ ProjectMeasuresQuery underTest = newProjectMeasuresQuery(criteria, emptySet());
+
+ assertThat(underTest.getQueryText().get()).isEqualTo("Sonar Qube");
+ }
+
+ @Test
+ public void create_query_having_q_ignore_case_sensitive() throws Exception {
+ List<Criterion> criteria = singletonList(Criterion.builder().setKey("query").setOperator(EQ).setValue("Sonar Qube").build());
+
+ ProjectMeasuresQuery underTest = newProjectMeasuresQuery(criteria, emptySet());
+
+ assertThat(underTest.getQueryText().get()).isEqualTo("Sonar Qube");
+ }
+
+ @Test
+ public void fail_to_create_query_having_q_with_no_value() throws Exception {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Query is invalid");
+
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("query").setOperator(EQ).build()),
+ emptySet());
+ }
+
+ @Test
+ public void fail_to_create_query_having_q_with_other_operator_than_equals() throws Exception {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Query should only be used with equals operator");
+
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("query").setOperator(LT).setValue("java").build()),
+ emptySet());
+ }
+
@Test
public void do_not_filter_on_projectUuids_if_criteria_non_empty_and_projectUuid_is_null() {
ProjectMeasuresQuery query = newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("ncloc").setOperator(EQ).setValue("10").build()),
assertThat(result.getComponentsList()).extracting(Component::getName).containsOnly("Sonar Java", "Sonar Groovy", "Sonar Qube");
}
+ @Test
+ public void filter_projects_by_text_query() {
+ OrganizationDto organizationDto = db.organizations().insertForKey("my-org-key-1");
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setKey("sonar-java").setName("Sonar Java"));
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setKey("sonar-groovy").setName("Sonar Groovy"));
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setKey("sonar-markdown").setName("Sonar Markdown"));
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setKey("sonarqube").setName("Sonar Qube"));
+
+ assertThat(call(request.setFilter("query = \"Groovy\"")).getComponentsList()).extracting(Component::getName).containsOnly("Sonar Groovy");
+ assertThat(call(request.setFilter("query = \"oNar\"")).getComponentsList()).extracting(Component::getName).containsOnly("Sonar Java", "Sonar Groovy", "Sonar Markdown", "Sonar Qube");
+ assertThat(call(request.setFilter("query = \"sonar-java\"")).getComponentsList()).extracting(Component::getName).containsOnly("Sonar Java");
+ }
+
@Test
public void filter_favourite_projects_with_query_with_or_without_a_specified_organization() {
userSession.logIn();
assertResults(new ProjectMeasuresQuery().setLanguages(newHashSet("unknown")));
}
+ @Test
+ public void filter_on_query_text() {
+ ComponentDto windows = newProjectDto(ORG).setUuid("windows").setName("Windows").setKey("project1");
+ ComponentDto apachee = newProjectDto(ORG).setUuid("apachee").setName("apachee").setKey("project2");
+ ComponentDto apache1 = newProjectDto(ORG).setUuid("apache-1").setName("Apache").setKey("project3");
+ ComponentDto apache2 = newProjectDto(ORG).setUuid("apache-2").setName("Apache").setKey("project4");
+ index(newDoc(windows), newDoc(apachee), newDoc(apache1), newDoc(apache2));
+
+ assertResults(new ProjectMeasuresQuery().setQueryText("windows"), windows);
+ assertResults(new ProjectMeasuresQuery().setQueryText("project2"), apachee);
+ assertResults(new ProjectMeasuresQuery().setQueryText("pAch"), apache1, apache2, apachee);
+ }
+
@Test
public void filter_on_ids() {
index(