aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacek <jacek.poreda@sonarsource.com>2020-03-30 15:51:39 +0200
committersonartech <sonartech@sonarsource.com>2020-04-15 20:03:38 +0000
commitc53ebc67f7c303397bf36c95aab306a0f17f27aa (patch)
tree06c82a22b5183c776e1049bbc5de3a18ba5fff1b
parentbd73de80860fda06408f7c342ba7455250b16151 (diff)
downloadsonarqube-c53ebc67f7c303397bf36c95aab306a0f17f27aa.tar.gz
sonarqube-c53ebc67f7c303397bf36c95aab306a0f17f27aa.zip
SONAR-13189 add 'qualifiers' filter and facet to WS
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java18
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java76
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java20
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java45
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java26
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java147
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java1
7 files changed, 314 insertions, 19 deletions
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
index cf23696d95a..029b9ffbca3 100644
--- a/server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
+++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
@@ -52,6 +52,7 @@ import org.elasticsearch.search.aggregations.metrics.sum.Sum;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.NestedSortBuilder;
import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Qualifiers;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.System2;
import org.sonar.core.util.stream.MoreCollectors;
@@ -127,6 +128,7 @@ import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUAGES;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_QUALIFIER;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_TAGS;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE;
@@ -158,6 +160,7 @@ public class ProjectMeasuresIndex {
NEW_SECURITY_HOTSPOTS_REVIEWED(new RangeMeasureFacet(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, SECURITY_REVIEW_RATING_THRESHOLDS)),
ALERT_STATUS(new MeasureFacet(ALERT_STATUS_KEY, ProjectMeasuresIndex::buildAlertStatusFacet)),
LANGUAGES(FILTER_LANGUAGES, FIELD_LANGUAGES, STICKY, ProjectMeasuresIndex::buildLanguageFacet),
+ QUALIFIER(FILTER_QUALIFIER, FIELD_QUALIFIER, STICKY, ProjectMeasuresIndex::buildQualifierFacet),
TAGS(FILTER_TAGS, FIELD_TAGS, STICKY, ProjectMeasuresIndex::buildTagsFacet);
private final String name;
@@ -342,6 +345,14 @@ public class ProjectMeasuresIndex {
.toArray(KeyedFilter[]::new));
}
+ private static AbstractAggregationBuilder<?> createQualifierFacet() {
+ return filters(
+ FILTER_QUALIFIER,
+ Stream.of(Qualifiers.APP, Qualifiers.PROJECT)
+ .map(qualifier -> new KeyedFilter(qualifier, termQuery(FIELD_QUALIFIER, qualifier)))
+ .toArray(KeyedFilter[]::new));
+ }
+
private AllFilters createFilters(ProjectMeasuresQuery query) {
AllFilters filters = RequestFiltersComputer.newAllFilters();
filters.addFilter(
@@ -593,4 +604,11 @@ public class ProjectMeasuresIndex {
NO_EXTRA_FILTER, extraSubAgg);
}
+ private static FilterAggregationBuilder buildQualifierFacet(Facet facet, ProjectMeasuresQuery query, TopAggregationHelper topAggregationHelper) {
+ return topAggregationHelper.buildTopAggregation(
+ facet.getName(), facet.getTopAggregationDef(),
+ NO_EXTRA_FILTER,
+ t -> t.subAggregation(createQualifierFacet()));
+ }
+
}
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
index a521daec2c3..f8ce765abf6 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
@@ -34,7 +34,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
-import org.sonar.api.resources.Qualifiers;
import org.sonar.api.utils.System2;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
@@ -73,6 +72,7 @@ import static org.sonar.db.user.UserTesting.newUserDto;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_QUALIFIER;
@RunWith(DataProviderRunner.class)
public class ProjectMeasuresIndexTest {
@@ -1377,6 +1377,80 @@ public class ProjectMeasuresIndexTest {
}
@Test
+ public void facet_qualifier() {
+ index(
+ // 2 docs with qualifier APP
+ newDoc().setQualifier(APP),
+ newDoc().setQualifier(APP),
+ // 4 docs with qualifier TRK
+ newDoc().setQualifier(PROJECT),
+ newDoc().setQualifier(PROJECT),
+ newDoc().setQualifier(PROJECT),
+ newDoc().setQualifier(PROJECT));
+
+ LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(FILTER_QUALIFIER)).getFacets().get(FILTER_QUALIFIER);
+
+ assertThat(result).containsOnly(
+ entry(APP, 2L),
+ entry(PROJECT, 4L));
+ }
+
+ @Test
+ public void facet_qualifier_is_sticky() {
+ index(
+ // 2 docs with qualifier APP
+ newDoc(NCLOC, 10d, COVERAGE, 0d).setQualifier(APP),
+ newDoc(NCLOC, 10d, COVERAGE, 0d).setQualifier(APP),
+ // 4 docs with qualifier TRK
+ newDoc(NCLOC, 100d, COVERAGE, 0d).setQualifier(PROJECT),
+ newDoc(NCLOC, 5000d, COVERAGE, 40d).setQualifier(PROJECT),
+ newDoc(NCLOC, 12000d, COVERAGE, 50d).setQualifier(PROJECT),
+ newDoc(NCLOC, 13000d, COVERAGE, 60d).setQualifier(PROJECT));
+
+ Facets facets = underTest.search(new ProjectMeasuresQuery()
+ .setQualifiers(Sets.newHashSet(PROJECT))
+ .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 55d)),
+ new SearchOptions().addFacets(FILTER_QUALIFIER, NCLOC)).getFacets();
+
+ // Sticky facet on qualifier does not take into account qualifier filter
+ assertThat(facets.get(FILTER_QUALIFIER)).containsOnly(
+ entry(APP, 2L),
+ entry(PROJECT, 3L));
+ // But facet on ncloc does well take into into filters
+ assertThat(facets.get(NCLOC)).containsExactly(
+ entry("*-1000.0", 1L),
+ entry("1000.0-10000.0", 1L),
+ entry("10000.0-100000.0", 1L),
+ entry("100000.0-500000.0", 0L),
+ entry("500000.0-*", 0L));
+ }
+
+ @Test
+ public void facet_qualifier_contains_only_app_and_projects_authorized_for_user() {
+ // User can see these projects
+ indexForUser(USER1,
+ // 3 docs with qualifier APP, PROJECT
+ newDoc().setQualifier(APP),
+ newDoc().setQualifier(APP),
+ newDoc().setQualifier(PROJECT));
+
+ // User cannot see these projects
+ indexForUser(USER2,
+ // 4 docs with qualifier PROJECT
+ newDoc().setQualifier(PROJECT),
+ newDoc().setQualifier(PROJECT),
+ newDoc().setQualifier(PROJECT),
+ newDoc().setQualifier(PROJECT));
+
+ userSession.logIn(USER1);
+ LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(FILTER_QUALIFIER)).getFacets().get(FILTER_QUALIFIER);
+
+ assertThat(result).containsOnly(
+ entry(APP, 2L),
+ entry(PROJECT, 1L));
+ }
+
+ @Test
public void facet_tags() {
index(
newDoc().setTags(newArrayList("finance", "offshore", "java")),
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
index ebe24c19fad..a804d8bab9d 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
@@ -20,6 +20,7 @@
package org.sonar.server.component.ws;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -27,21 +28,24 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.api.measures.Metric.Level;
+import org.sonar.api.resources.Qualifiers;
import org.sonar.server.component.ws.FilterParser.Criterion;
-import org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
import org.sonar.server.measure.index.ProjectMeasuresQuery;
+import org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static java.util.Collections.singleton;
import static java.util.Locale.ENGLISH;
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.EQ;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.IN;
-import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUAGES;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_QUALIFIER;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_TAGS;
class ProjectMeasuresQueryFactory {
@@ -54,6 +58,7 @@ class ProjectMeasuresQueryFactory {
.put(IS_FAVORITE_CRITERION.toLowerCase(ENGLISH), (criterion, query) -> processIsFavorite(criterion))
.put(FILTER_LANGUAGES, ProjectMeasuresQueryFactory::processLanguages)
.put(FILTER_TAGS, ProjectMeasuresQueryFactory::processTags)
+ .put(FILTER_QUALIFIER, ProjectMeasuresQueryFactory::processQualifier)
.put(QUERY_KEY, ProjectMeasuresQueryFactory::processQuery)
.put(ALERT_STATUS_KEY, ProjectMeasuresQueryFactory::processQualityGateStatus)
.build();
@@ -110,6 +115,17 @@ class ProjectMeasuresQueryFactory {
throw new IllegalArgumentException("Tags should be set either by using 'tags = java' or 'tags IN (finance, platform)'");
}
+ private static void processQualifier(Criterion criterion, ProjectMeasuresQuery query) {
+ checkOperator(criterion);
+ checkValue(criterion);
+ Operator operator = criterion.getOperator();
+ String value = criterion.getValue();
+ checkArgument(EQ.equals(operator), "Only equals operator is available for qualifier criteria");
+ String qualifier = Stream.of(Qualifiers.APP, Qualifiers.PROJECT).filter(q -> q.equalsIgnoreCase(value)).findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(format("Unknown qualifier : '%s'", value)));
+ query.setQualifiers(Sets.newHashSet(qualifier));
+ }
+
private static void processQuery(Criterion criterion, ProjectMeasuresQuery query) {
checkOperator(criterion);
Operator operatorValue = criterion.getOperator();
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
index e1648e9bdf9..7874d771e9b 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
@@ -135,6 +135,7 @@ public class SearchProjectsAction implements ComponentsWsAction {
.addPagingParams(DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE)
.setInternal(true)
.setChangelog(
+ new Change("8.3", "Add 'qualifier' filter and facet"),
new Change("8.0", "Field 'id' from response has been removed"))
.setResponseExample(getClass().getResource("search_projects-example.json"))
.setHandler(this);
@@ -159,49 +160,55 @@ public class SearchProjectsAction implements ComponentsWsAction {
.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>" +
+ HTML_UL_START_TAG +
" <li>to filter my favorite projects with a failed quality gate and a coverage greater than or equals to 60% and a coverage strictly lower than 80%:<br>" +
" <code>filter=\"alert_status = ERROR and isFavorite and coverage >= 60 and coverage < 80\"</code></li>" +
" <li>to filter projects with a reliability, security and maintainability rating equals or worse than B:<br>" +
" <code>filter=\"reliability_rating>=2 and security_rating>=2 and sqale_rating>=2\"</code></li>" +
" <li>to filter projects without duplication data:<br>" +
" <code>filter=\"duplicated_lines_density = NO_DATA\"</code></li>" +
- "</ul>" +
+ HTML_UL_END_TAG +
"To filter on project name or key, use the 'query' keyword, for instance : <code>filter='query = \"Sonar\"'</code>.<br>" +
"<br>" +
"To filter on a numeric metric, provide the metric key.<br>" +
"These are the supported metric keys:<br>" +
- "<ul>" +
+ HTML_UL_START_TAG +
METRIC_KEYS.stream().sorted().map(key -> "<li>" + key + "</li>").collect(Collectors.joining()) +
- "</ul>" +
+ HTML_UL_END_TAG +
"<br>" +
"To filter on a rating, provide the corresponding metric key (ex: reliability_rating for reliability rating).<br>" +
"The possible values are:" +
- "<ul>" +
+ HTML_UL_START_TAG +
" <li>'1' for rating A</li>" +
" <li>'2' for rating B</li>" +
" <li>'3' for rating C</li>" +
" <li>'4' for rating D</li>" +
" <li>'5' for rating E</li>" +
- "</ul>" +
+ HTML_UL_END_TAG +
"To filter on a Quality Gate status use the metric key 'alert_status'. Only the '=' operator can be used.<br>" +
"The possible values are:" +
- "<ul>" +
+ HTML_UL_START_TAG +
" <li>'OK' for Passed</li>" +
" <li>'WARN' for Warning</li>" +
" <li>'ERROR' for Failed</li>" +
- "</ul>" +
+ HTML_UL_END_TAG +
"To filter on language keys use the language key: " +
- "<ul>" +
+ HTML_UL_START_TAG +
" <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>" +
+ HTML_UL_END_TAG +
"Use the WS api/languages/list to find the key of a language.<br> " +
"To filter on tags use the 'tag' keyword:" +
- "<ul> " +
+ HTML_UL_START_TAG +
" <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>");
+ HTML_UL_END_TAG +
+ "To filter on a qualifier use key 'qualifier'. Only the '=' operator can be used.<br>" +
+ "The possible values are:" +
+ HTML_UL_START_TAG +
+ " <li>TRK - for projects</li>" +
+ " <li>APP - for applications</li>" +
+ HTML_UL_END_TAG);
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.",
ALERT_STATUS_KEY, SORT_BY_LAST_ANALYSIS_DATE, PARAM_FILTER)
@@ -294,6 +301,20 @@ public class SearchProjectsAction implements ComponentsWsAction {
}
}
+ private void filterQualifiersBasedOnEdition(ProjectMeasuresQuery query) {
+ Set<String> availableQualifiers = getQualifiersFromEdition();
+ Set<String> requestQualifiers = query.getQualifiers().orElse(availableQualifiers);
+
+ Set<String> resolvedQualifiers = requestQualifiers.stream()
+ .filter(availableQualifiers::contains)
+ .collect(Collectors.toSet());
+ if (!resolvedQualifiers.isEmpty()) {
+ query.setQualifiers(resolvedQualifiers);
+ } else {
+ throw new IllegalArgumentException("Invalid qualifier, available are: " + String.join(",", availableQualifiers));
+ }
+ }
+
private Set<String> getQualifiersFromEdition() {
Optional<Edition> edition = editionProvider.get();
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
index 797ca5d9f35..1c97e1c4d37 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
@@ -35,13 +35,13 @@ import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.EQ;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.GT;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.IN;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.LT;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.LTE;
-import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
public class ProjectMeasuresQueryFactoryTest {
@@ -117,6 +117,30 @@ public class ProjectMeasuresQueryFactoryTest {
}
@Test
+ public void create_query_on_qualifier() {
+ ProjectMeasuresQuery query = newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("qualifier").setOperator(EQ).setValue("APP").build()),
+ emptySet());
+
+ assertThat(query.getQualifiers().get()).containsOnly("APP");
+ }
+
+ @Test
+ public void fail_to_create_query_on_qualifier_when_operator_is_not_equal() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Only equals operator is available for qualifier criteria");
+
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("qualifier").setOperator(GT).setValue("APP").build()), emptySet());
+ }
+
+ @Test
+ public void fail_to_create_query_on_qualifier_when_value_is_incorrect() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Unknown qualifier : 'unknown'");
+
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("qualifier").setOperator(EQ).setValue("unknown").build()), emptySet());
+ }
+
+ @Test
public void create_query_on_language_using_in_operator() {
ProjectMeasuresQuery query = newProjectMeasuresQuery(
singletonList(Criterion.builder().setKey("languages").setOperator(IN).setValues(asList("java", "js")).build()),
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
index 0f19d36630c..a8b00a512b8 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
@@ -70,6 +70,7 @@ import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -102,6 +103,7 @@ import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUAGES;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_QUALIFIER;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_TAGS;
@RunWith(DataProviderRunner.class)
@@ -144,6 +146,22 @@ public class SearchProjectsActionTest {
};
}
+ @DataProvider
+ public static Object[][] community_or_developer_edition() {
+ return new Object[][] {
+ {Edition.COMMUNITY},
+ {Edition.DEVELOPER},
+ };
+ }
+
+ @DataProvider
+ public static Object[][] enterprise_or_datacenter_edition() {
+ return new Object[][] {
+ {Edition.ENTERPRISE},
+ {Edition.DATACENTER},
+ };
+ }
+
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();
@@ -172,7 +190,7 @@ public class SearchProjectsActionTest {
assertThat(def.isPost()).isFalse();
assertThat(def.responseExampleAsString()).isNotEmpty();
assertThat(def.params().stream().map(Param::key).collect(toList())).containsOnly("organization", "filter", "facets", "s", "asc", "ps", "p", "f");
- assertThat(def.changelog()).hasSize(1);
+ assertThat(def.changelog()).hasSize(2);
Param organization = def.param("organization");
assertThat(organization.isRequired()).isFalse();
@@ -215,7 +233,7 @@ public class SearchProjectsActionTest {
Param facets = def.param("facets");
assertThat(facets.defaultValue()).isNull();
assertThat(facets.possibleValues()).containsOnly("ncloc", "duplicated_lines_density", "coverage", "sqale_rating", "reliability_rating", "security_rating", "alert_status",
- "languages", "tags", "new_reliability_rating", "new_security_rating", "new_maintainability_rating", "new_coverage", "new_duplicated_lines_density", "new_lines",
+ "languages", "tags", "qualifier", "new_reliability_rating", "new_security_rating", "new_maintainability_rating", "new_coverage", "new_duplicated_lines_density", "new_lines",
"security_review_rating", "security_hotspots_reviewed", "new_security_hotspots_reviewed", "new_security_review_rating");
}
@@ -620,7 +638,7 @@ public class SearchProjectsActionTest {
@Test
@UseDataProvider("component_qualifiers_for_valid_editions")
- public void filter_projects_and_apps_by_editions(String[] qualifiers, Edition edition) {
+ public void default_filter_projects_and_apps_by_editions(String[] qualifiers, Edition edition) {
when(editionProviderMock.get()).thenReturn(Optional.of(edition));
userSession.logIn();
OrganizationDto organization = db.organizations().insert();
@@ -676,6 +694,81 @@ public class SearchProjectsActionTest {
}
@Test
+ @UseDataProvider("enterprise_or_datacenter_edition")
+ public void filter_projects_and_apps_by_APP_qualifier_when_ee_dc(Edition edition) {
+ when(editionProviderMock.get()).thenReturn(Optional.of(edition));
+ userSession.logIn();
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto application1 = insertApplication(organization);
+ ComponentDto application2 = insertApplication(organization);
+ ComponentDto application3 = insertApplication(organization);
+
+ insertProject(organization);
+ insertProject(organization);
+ insertProject(organization);
+
+ SearchProjectsWsResponse result = call(request.setFilter("qualifier = APP"));
+
+ assertThat(result.getComponentsCount())
+ .isEqualTo(3);
+
+ assertThat(result.getComponentsList()).extracting(Component::getKey)
+ .containsExactly(
+ Stream.of(application1, application2, application3)
+ .map(ComponentDto::getDbKey)
+ .toArray(String[]::new));
+ }
+
+ @Test
+ @UseDataProvider("enterprise_or_datacenter_edition")
+ public void filter_projects_and_apps_by_TRK_qualifier_when_ee_or_dc(Edition edition) {
+ when(editionProviderMock.get()).thenReturn(Optional.of(edition));
+ userSession.logIn();
+ OrganizationDto organization = db.organizations().insert();
+
+ insertApplication(organization);
+ insertApplication(organization);
+ insertApplication(organization);
+
+ ComponentDto project1 = insertProject(organization);
+ ComponentDto project2 = insertProject(organization);
+ ComponentDto project3 = insertProject(organization);
+
+ SearchProjectsWsResponse result = call(request.setFilter("qualifier = TRK"));
+
+ assertThat(result.getComponentsCount())
+ .isEqualTo(3);
+
+ assertThat(result.getComponentsList()).extracting(Component::getKey)
+ .containsExactly(
+ Stream.of(project1, project2, project3)
+ .map(ComponentDto::getDbKey)
+ .toArray(String[]::new));
+ }
+
+ @Test
+ @UseDataProvider("community_or_developer_edition")
+ public void fail_when_qualifier_filter_by_APP_set_when_ce_or_de(Edition edition) {
+ when(editionProviderMock.get()).thenReturn(Optional.of(edition));
+ userSession.logIn();
+ OrganizationDto organization = db.organizations().insert();
+
+ assertThatThrownBy(() -> call(request.setFilter("qualifiers = APP")))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ @UseDataProvider("enterprise_or_datacenter_edition")
+ public void fail_when_qualifier_filter_invalid_when_ee_or_dc(Edition edition) {
+ when(editionProviderMock.get()).thenReturn(Optional.of(edition));
+ userSession.logIn();
+ OrganizationDto organization = db.organizations().insert();
+
+ assertThatThrownBy(() -> call(request.setFilter("qualifiers = BLA")))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
public void do_not_return_isFavorite_if_anonymous_user() {
userSession.anonymous();
OrganizationDto organization = db.organizations().insert();
@@ -823,6 +916,54 @@ public class SearchProjectsActionTest {
}
@Test
+ public void return_qualifiers_facet() {
+ when(editionProviderMock.get()).thenReturn(Optional.of(Edition.ENTERPRISE));
+ userSession.logIn();
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto application1 = insertApplication(organization);
+ ComponentDto application2 = insertApplication(organization);
+ ComponentDto application3 = insertApplication(organization);
+ ComponentDto application4 = insertApplication(organization);
+
+ ComponentDto project1 = insertProject(organization);
+ ComponentDto project2 = insertProject(organization);
+ ComponentDto project3 = insertProject(organization);
+
+ SearchProjectsWsResponse result = call(request.setFacets(singletonList(FILTER_QUALIFIER)));
+
+ Common.Facet facet = result.getFacets().getFacetsList().stream()
+ .filter(oneFacet -> FILTER_QUALIFIER.equals(oneFacet.getProperty()))
+ .findFirst().orElseThrow(IllegalStateException::new);
+ assertThat(facet.getValuesList())
+ .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
+ .containsExactly(
+ tuple("APP", 4L),
+ tuple("TRK", 3L));
+ }
+
+ @Test
+ public void return_qualifiers_facet_with_qualifiers_having_no_project_if_qualifiers_is_in_filter() {
+ when(editionProviderMock.get()).thenReturn(Optional.of(Edition.ENTERPRISE));
+ userSession.logIn();
+ OrganizationDto organization = db.getDefaultOrganization();
+ ComponentDto application1 = insertApplication(organization);
+ ComponentDto application2 = insertApplication(organization);
+ ComponentDto application3 = insertApplication(organization);
+ ComponentDto application4 = insertApplication(organization);
+
+ SearchProjectsWsResponse result = call(request.setFilter("qualifier = APP").setFacets(singletonList(FILTER_QUALIFIER)));
+
+ Common.Facet facet = result.getFacets().getFacetsList().stream()
+ .filter(oneFacet -> FILTER_QUALIFIER.equals(oneFacet.getProperty()))
+ .findFirst().orElseThrow(IllegalStateException::new);
+ assertThat(facet.getValuesList())
+ .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
+ .containsExactly(
+ tuple("APP", 4L),
+ tuple("TRK", 0L));
+ }
+
+ @Test
@UseDataProvider("rating_metric_keys")
public void return_rating_facet(String ratingMetricKey) {
userSession.logIn();
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java
index e3af64ce64d..865abce2a55 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java
@@ -51,6 +51,7 @@ public class ProjectsWsParameters {
public static final String FILTER_LANGUAGES = "languages";
public static final String FILTER_TAGS = "tags";
+ public static final String FILTER_QUALIFIER = "qualifier";
private ProjectsWsParameters() {
// static utils only