aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2017-03-02 15:39:08 +0100
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2017-03-03 11:07:45 +0100
commit31e88758527886fa7b66508c0c8d6afd57db4868 (patch)
tree7eac382dc952dff435219e93228cce899c554fd1
parent3c513b0d4e033ad8837ffdef8896c0f86931ed98 (diff)
downloadsonarqube-31e88758527886fa7b66508c0c8d6afd57db4868.tar.gz
sonarqube-31e88758527886fa7b66508c0c8d6afd57db4868.zip
SONAR-8838 Filter by tags in WS api/components/search_projects
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java34
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java17
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java10
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java39
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java26
6 files changed, 104 insertions, 24 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java
index c352c622f7e..9a4a3f0219b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java
@@ -39,7 +39,7 @@ public class FilterParser {
private static final String DOUBLE_QUOTES = "\"";
private static final Splitter CRITERIA_SPLITTER = Splitter.on(Pattern.compile(" and ", Pattern.CASE_INSENSITIVE)).trimResults().omitEmptyStrings();
- private static final Splitter IN_VALUES_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
+ private static final Splitter IN_VALUES_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings();
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);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
index dc2e9330b1e..e05cb940b37 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
@@ -26,6 +26,7 @@ import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.api.measures.Metric.Level;
+import org.sonar.server.component.ws.FilterParser.Criterion;
import org.sonar.server.component.ws.FilterParser.Operator;
import org.sonar.server.measure.index.ProjectMeasuresQuery;
@@ -42,20 +43,21 @@ import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUA
class ProjectMeasuresQueryFactory {
public static final String IS_FAVORITE_CRITERION = "isFavorite";
+ public static final String CRITERION_TAG = "tag";
public static final String QUERY_KEY = "query";
private ProjectMeasuresQueryFactory() {
// prevent instantiation
}
- static ProjectMeasuresQuery newProjectMeasuresQuery(List<FilterParser.Criterion> criteria, @Nullable Set<String> projectUuids) {
+ static ProjectMeasuresQuery newProjectMeasuresQuery(List<Criterion> criteria, @Nullable Set<String> projectUuids) {
ProjectMeasuresQuery query = new ProjectMeasuresQuery();
Optional.ofNullable(projectUuids).ifPresent(query::setProjectUuids);
criteria.forEach(criterion -> processCriterion(criterion, query));
return query;
}
- private static void processCriterion(FilterParser.Criterion criterion, ProjectMeasuresQuery query) {
+ private static void processCriterion(Criterion criterion, ProjectMeasuresQuery query) {
String key = criterion.getKey().toLowerCase(ENGLISH);
if (IS_FAVORITE_CRITERION.equalsIgnoreCase(key)) {
return;
@@ -63,11 +65,17 @@ class ProjectMeasuresQueryFactory {
Operator operator = criterion.getOperator();
checkArgument(operator != null, "Operator cannot be null for '%s'", key);
+
if (FILTER_LANGUAGE.equalsIgnoreCase(key)) {
processLanguages(criterion, query);
return;
}
+ if (CRITERION_TAG.equalsIgnoreCase(key)) {
+ processTags(criterion, query);
+ return;
+ }
+
if (QUERY_KEY.equalsIgnoreCase(key)) {
processQuery(criterion, query);
return;
@@ -82,7 +90,7 @@ class ProjectMeasuresQueryFactory {
}
}
- private static void processLanguages(FilterParser.Criterion criterion, ProjectMeasuresQuery query) {
+ private static void processLanguages(Criterion criterion, ProjectMeasuresQuery query) {
Operator operator = criterion.getOperator();
String value = criterion.getValue();
List<String> values = criterion.getValues();
@@ -97,7 +105,22 @@ class ProjectMeasuresQueryFactory {
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) {
+ private static void processTags(Criterion criterion, ProjectMeasuresQuery query) {
+ Operator operator = criterion.getOperator();
+ String value = criterion.getValue();
+ List<String> values = criterion.getValues();
+ if (value != null && EQ.equals(operator)) {
+ query.setTags(singleton(value));
+ return;
+ }
+ if (!values.isEmpty() && IN.equals(operator)) {
+ query.setTags(new HashSet<>(values));
+ return;
+ }
+ throw new IllegalArgumentException("Tag should be set either by using 'tag = java' or 'tag IN (finance, platform)'");
+ }
+
+ private static void processQuery(Criterion criterion, ProjectMeasuresQuery query) {
Operator operatorValue = criterion.getOperator();
String value = criterion.getValue();
checkArgument(value != null, "Query is invalid");
@@ -105,7 +128,7 @@ class ProjectMeasuresQueryFactory {
query.setQueryText(value);
}
- private static void processQualityGateStatus(FilterParser.Criterion criterion, ProjectMeasuresQuery query) {
+ private static void processQualityGateStatus(Criterion criterion, ProjectMeasuresQuery query) {
Operator operator = criterion.getOperator();
String value = criterion.getValue();
checkArgument(EQ.equals(operator), "Only equals operator is available for quality gate criteria");
@@ -121,5 +144,4 @@ class ProjectMeasuresQueryFactory {
throw new IllegalArgumentException(format("Value '%s' is not a number", value));
}
}
-
}
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 b5749087c02..493060331d7 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
@@ -119,7 +119,7 @@ public class SearchProjectsAction implements ComponentsWsAction {
.setPossibleValues(SUPPORTED_FACETS);
action
.createParam(PARAM_FILTER)
- .setDescription("Filter of projects on name, key, measure value, quality gate, language, or whether a project is a favorite or not.<br>" +
+ .setDescription("Filter of projects on name, key, measure value, quality gate, language, tag or whether a project is a favorite or not.<br>" +
"The filter must be encoded to form a valid URL (for example '=' must be replaced by '%3D').<br>" +
"Examples of use:" +
"<ul>" +
@@ -149,12 +149,17 @@ public class SearchProjectsAction implements ComponentsWsAction {
" <li>'WARN' for Warning</li>" +
" <li>'ERROR' for Failed</li>" +
"</ul>" +
- "To filter on language keys use language key' : " +
+ "To filter on language keys use the language key: " +
"<ul>" +
- " <li>To filter on a single language you can use 'language = java'</li>" +
- " <li>To filter on a many language you must use 'language IN (java, js)'</li>" +
- "<ul/>" +
- "Use the WS api/languages/list to find the key of a language.");
+ " <li>to filter on a single language you can use 'language = java'</li>" +
+ " <li>to filter on several languages you must use 'language IN (java, js)'</li>" +
+ "</ul>" +
+ "Use the WS api/languages/list to find the key of a language." +
+ "To filter on tags use the 'tag' keyword:" +
+ "<ul> " +
+ " <li>to filter on one tag you can use <code>tag = finance</code></li>" +
+ " <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'), or by project name.<br/>" +
"See '%s' parameter description for the possible metric values", ALERT_STATUS_KEY, PARAM_FILTER)
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java
index 27a429f8fe4..fa83fae3169 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java
@@ -126,14 +126,14 @@ public class FilterParserTest {
@Test
public void parse_filter_without_any_space_in_criteria() throws Exception {
- List<Criterion> criterion = FilterParser.parse("ncloc>10 and coverage<=80 and language in (java,js)");
+ List<Criterion> criterion = FilterParser.parse("ncloc>10 and coverage<=80 and tags in (java,platform)");
assertThat(criterion)
.extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue, Criterion::getValues)
.containsOnly(
tuple("ncloc", GT, "10", emptyList()),
tuple("coverage", LTE, "80", emptyList()),
- tuple("language", IN, null, asList("java", "js")));
+ tuple("tags", IN, null, asList("java", "platform")));
}
@Test
@@ -198,4 +198,10 @@ public class FilterParserTest {
assertThat(criterion).hasSize(4);
}
+ @Test
+ public void metric_key_with_and_string() {
+ List<Criterion> criterion = FilterParser.parse("ncloc > 10 and operand = 5");
+
+ assertThat(criterion).hasSize(2).extracting(Criterion::getKey, Criterion::getValue).containsExactly(tuple("ncloc", "10"), tuple("operand", "5"));
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
index 215949fba57..46ec80c2d7d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
@@ -140,8 +140,7 @@ public class ProjectMeasuresQueryFactoryTest {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Language should be set either by using 'language = java' or 'language IN (java, js)");
- newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("language").setOperator(IN).setValue("java").build()),
- emptySet());
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("language").setOperator(IN).setValue("java").build()), emptySet());
}
@Test
@@ -149,8 +148,42 @@ public class ProjectMeasuresQueryFactoryTest {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Language should be set either by using 'language = java' or 'language IN (java, js)");
- newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("language").setOperator(EQ).setValues(asList("java")).build()),
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("language").setOperator(EQ).setValues(asList("java")).build()), emptySet());
+ }
+
+
+ @Test
+ public void create_query_on_tag_using_in_operator() throws Exception {
+ ProjectMeasuresQuery query = newProjectMeasuresQuery(
+ singletonList(Criterion.builder().setKey("tag").setOperator(IN).setValues(asList("java", "js")).build()),
+ emptySet());
+
+ assertThat(query.getTags().get()).containsOnly("java", "js");
+ }
+
+ @Test
+ public void create_query_on_tag_using_equals_operator() throws Exception {
+ ProjectMeasuresQuery query = newProjectMeasuresQuery(
+ singletonList(Criterion.builder().setKey("tag").setOperator(EQ).setValue("java").build()),
emptySet());
+
+ assertThat(query.getTags().get()).containsOnly("java");
+ }
+
+ @Test
+ public void fail_to_create_query_on_tag_using_in_operator_and_value() throws Exception {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Tag should be set either by using 'tag = java' or 'tag IN (finance, platform)");
+
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("tag").setOperator(IN).setValue("java").build()), emptySet());
+ }
+
+ @Test
+ public void fail_to_create_query_on_tag_using_eq_operator_and_values() throws Exception {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Tag should be set either by using 'tag = java' or 'tag IN (finance, platform)");
+
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("tag").setOperator(EQ).setValues(asList("java")).build()), emptySet());
}
@Test
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 48f9e835d0f..605442cd4a4 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
@@ -160,8 +160,7 @@ public class SearchProjectsActionTest {
.setUuid(Uuids.UUID_EXAMPLE_01)
.setKey(KeyExamples.KEY_PROJECT_EXAMPLE_001)
.setName("My Project 1")
- .setTagsString("finance, java")
- );
+ .setTagsString("finance, java"));
insertProjectInDbAndEs(newProjectDto(organization1Dto)
.setUuid(Uuids.UUID_EXAMPLE_02)
.setKey(KeyExamples.KEY_PROJECT_EXAMPLE_002)
@@ -170,8 +169,7 @@ public class SearchProjectsActionTest {
.setUuid(Uuids.UUID_EXAMPLE_03)
.setKey(KeyExamples.KEY_PROJECT_EXAMPLE_003)
.setName("My Project 3")
- .setTagsString("sales, offshore, java")
- );
+ .setTagsString("sales, offshore, java"));
userSession.logIn().setUserId(23);
addFavourite(project1);
dbSession.commit();
@@ -298,6 +296,20 @@ public class SearchProjectsActionTest {
}
@Test
+ public void filter_projects_by_tags() {
+ OrganizationDto organizationDto = db.organizations().insertForKey("my-org-key-1");
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setName("Sonar Java").setTags(newArrayList("finance", "platform")));
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setName("Sonar Markdown").setTags(singletonList("marketing")));
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setName("Sonar Qube").setTags(newArrayList("offshore")));
+ insertMetrics(COVERAGE, NCLOC);
+ request.setFilter("tag in (finance, offshore)");
+
+ SearchProjectsWsResponse result = call(request);
+
+ assertThat(result.getComponentsList()).extracting(Component::getName).containsOnly("Sonar Java", "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"));
@@ -306,7 +318,8 @@ public class SearchProjectsActionTest {
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 = \"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");
}
@@ -601,7 +614,8 @@ public class SearchProjectsActionTest {
.setName(project.name())
.setMeasures(measures)
.setQualityGateStatus(qualityGateStatus)
- .setLanguages(languagesDistribution));
+ .setLanguages(languagesDistribution)
+ .setTags(project.getTags()));
authorizationIndexerTester.allowOnlyAnyone(project);
} catch (Exception e) {
Throwables.propagate(e);