]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7578 Filter issues by leak period and by file, directory or module 996/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 18 May 2016 13:42:14 +0000 (15:42 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 19 May 2016 10:50:01 +0000 (12:50 +0200)
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java

index 4846bb03af208e9b40ae9b33fd357b759c011adb..fa98d0fc3f29c27a4bb9763a9484997d62b46cec 100644 (file)
@@ -21,20 +21,24 @@ package org.sonar.server.issue;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.BooleanUtils;
 import org.apache.commons.lang.ObjectUtils;
@@ -60,6 +64,8 @@ import org.sonarqube.ws.client.issue.IssueFilterParameters;
 import org.sonarqube.ws.client.issue.SearchWsRequest;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.FluentIterable.from;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.lang.String.format;
 import static org.sonar.api.utils.DateUtils.longToDate;
@@ -68,6 +74,10 @@ import static org.sonar.db.component.ComponentDtoFunctions.toProjectUuid;
 import static org.sonar.db.component.ComponentDtoFunctions.toUuid;
 import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 import static org.sonar.server.ws.WsUtils.checkRequest;
+import static org.sonarqube.ws.client.issue.IssueFilterParameters.COMPONENTS;
+import static org.sonarqube.ws.client.issue.IssueFilterParameters.COMPONENT_KEYS;
+import static org.sonarqube.ws.client.issue.IssueFilterParameters.COMPONENT_ROOTS;
+import static org.sonarqube.ws.client.issue.IssueFilterParameters.COMPONENT_UUIDS;
 import static org.sonarqube.ws.client.issue.IssueFilterParameters.CREATED_AFTER;
 import static org.sonarqube.ws.client.issue.IssueFilterParameters.CREATED_IN_LAST;
 import static org.sonarqube.ws.client.issue.IssueFilterParameters.SINCE_LEAK_PERIOD;
@@ -206,7 +216,7 @@ public class IssueQueryService {
         request.getFileUuids(),
         request.getAuthors());
 
-      builder.createdAfter(buildCreatedAfterFromRequest(session, request, allComponentUuids, effectiveOnComponentOnly));
+      builder.createdAfter(buildCreatedAfterFromRequest(session, request, allComponentUuids));
 
       String sort = request.getSort();
       if (!Strings.isNullOrEmpty(sort)) {
@@ -220,7 +230,7 @@ public class IssueQueryService {
     }
   }
 
-  private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, Set<String> componentUuids, boolean effectiveOnComponentOnly) {
+  private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, Set<String> componentUuids) {
     Date createdAfter = parseAsDateTime(request.getCreatedAfter());
     String createdInLast = request.getCreatedInLast();
 
@@ -229,29 +239,15 @@ public class IssueQueryService {
     }
 
     checkRequest(createdAfter == null, "'%s' and '%s' cannot be set simultaneously", CREATED_AFTER, SINCE_LEAK_PERIOD);
-    Set<String> allComponentUuids = new HashSet<>(componentUuids);
-    if (!effectiveOnComponentOnly) {
-      if (request.getProjectKeys() != null) {
-        allComponentUuids.addAll(componentUuids(dbSession, request.getProjectKeys()));
-      }
-      if (request.getProjectUuids() != null) {
-        allComponentUuids.addAll(request.getProjectUuids());
-      }
-      if (request.getModuleUuids() != null) {
-        allComponentUuids.addAll(request.getModuleUuids());
-      }
-      if (request.getFileUuids() != null) {
-        allComponentUuids.addAll(request.getFileUuids());
-      }
-    }
 
-    checkArgument(allComponentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
-    String uuid = allComponentUuids.iterator().next();
+    checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
+    String uuid = componentUuids.iterator().next();
     // TODO use ComponentFinder instead
     Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, uuid);
     return buildCreatedAfterFromDates(createdAfterFromSnapshot, createdInLast);
   }
 
+  @CheckForNull
   private Date findCreatedAfterFromComponentUuid(DbSession dbSession, String uuid) {
     ComponentDto component = checkFoundWithOptional(componentService.getByUuid(uuid), "Component with id '%s' not found", uuid);
     SnapshotDto snapshot = dbClient.snapshotDao().selectLastSnapshotByComponentId(dbSession, component.getId());
@@ -285,9 +281,9 @@ public class IssueQueryService {
     Set<String> allComponentUuids) {
     boolean effectiveOnComponentOnly = false;
 
-    failIfBothParametersSet(componentRootUuids, componentRoots, "componentRoots and componentRootUuids cannot be set simultaneously");
-    failIfBothParametersSet(componentUuids, components, "components and componentUuids cannot be set simultaneously");
-    failIfBothParametersSet(componentKeys, componentUuids, "componentKeys and componentUuids cannot be set simultaneously");
+    checkArgument(atMostOneNonNullElement(components, componentUuids, componentKeys, componentRootUuids, componentRoots),
+      "At most one of the following parameters can be provided: %s, %s, %s, %s, %s",
+      COMPONENT_KEYS, COMPONENT_UUIDS, COMPONENTS, COMPONENT_ROOTS, COMPONENT_UUIDS);
 
     if (componentRootUuids != null) {
       allComponentUuids.addAll(componentRootUuids);
@@ -308,10 +304,10 @@ public class IssueQueryService {
     return effectiveOnComponentOnly;
   }
 
-  private void failIfBothParametersSet(@Nullable Collection<String> uuids, @Nullable Collection<String> keys, String message) {
-    if (uuids != null && keys != null) {
-      throw new IllegalArgumentException(message);
-    }
+  private static boolean atMostOneNonNullElement(Object... objects) {
+    return !from(Arrays.asList(objects))
+      .filter(notNull())
+      .anyMatch(new HasTwoOrMoreElements());
   }
 
   private void addComponentParameters(IssueQuery.Builder builder, DbSession session,
@@ -330,7 +326,7 @@ public class IssueQueryService {
     }
 
     builder.authors(authors);
-    failIfBothParametersSet(projectUuids, projects, "projects and projectUuids cannot be set simultaneously");
+    checkArgument(projectUuids == null || projects == null, "projects and projectUuids cannot be set simultaneously");
     if (projectUuids != null) {
       builder.projectUuids(projectUuids);
     } else {
@@ -477,4 +473,18 @@ public class IssueQueryService {
       }
     }
   }
+
+  private static class HasTwoOrMoreElements implements Predicate<Object> {
+    private AtomicInteger counter;
+
+    private HasTwoOrMoreElements() {
+      this.counter = new AtomicInteger();
+    }
+
+    @Override
+    public boolean apply(@Nonnull Object input) {
+      Objects.requireNonNull(input);
+      return counter.incrementAndGet() >= 2;
+    }
+  }
 }
index 94796e7e8b9ec2689e6b82054ce55d4b02ae976a..f8f4602bf3769b84e59c7cde705aebbf9ea353ee 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.issue.ws;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Lists;
-import com.google.common.io.Resources;
 import java.util.Collection;
 import java.util.EnumSet;
 import java.util.LinkedHashMap;
@@ -122,12 +121,14 @@ public class SearchAction implements IssuesWsAction {
       .createAction(SEARCH_ACTION)
       .setHandler(this)
       .setDescription(
-        "Search for issues. Requires Browse permission on project(s).<br/>" +
-          "Since 5.5, response field 'debt' has been renamed to 'effort'.<br/>" +
-          "Since 5.5, response field 'actionPlan' has been removed.<br/>" +
-          "Since 5.5, response field 'reporter' has been removed, as manual issue feature has been dropped")
+        "Search for issues. Requires Browse permission on project(s).<br>" +
+          "At most one of the following parameters can be provided at the same time: %s, %s, %s, %s, %s<br>" +
+          "Since 5.5, response field 'debt' has been renamed to 'effort'.<br>" +
+          "Since 5.5, response field 'actionPlan' has been removed.<br>" +
+          "Since 5.5, response field 'reporter' has been removed, as manual issue feature has been dropped.",
+        COMPONENT_KEYS, COMPONENT_UUIDS, COMPONENTS, COMPONENT_ROOT_UUIDS, COMPONENT_ROOTS)
       .setSince("3.6")
-      .setResponseExample(Resources.getResource(this.getClass(), "example-search.json"));
+      .setResponseExample(getClass().getResource("example-search.json"));
 
     action.addPagingParams(100, MAX_LIMIT);
     action.createParam(Param.FACETS)
@@ -231,8 +232,7 @@ public class SearchAction implements IssuesWsAction {
 
     action.createParam(COMPONENT_KEYS)
       .setDescription("To retrieve issues associated to a specific list of components sub-components (comma-separated list of component keys). " +
-        "A component can be a view, developer, project, module, directory or file. " +
-        "If this parameter is set, componentUuids must not be set.")
+        "A component can be a view, developer, project, module, directory or file.")
       .setExampleValue(KEY_PROJECT_EXAMPLE_001);
     action.createParam(COMPONENTS)
       .setDeprecatedSince("5.1")
@@ -240,10 +240,8 @@ public class SearchAction implements IssuesWsAction {
     action.createParam(COMPONENT_UUIDS)
       .setDescription("To retrieve issues associated to a specific list of components their sub-components (comma-separated list of component UUIDs). " +
         INTERNAL_PARAMETER_DISCLAIMER +
-        "A component can be a project, module, directory or file. " +
-        "If this parameter is set, componentKeys must not be set.")
+        "A component can be a project, module, directory or file.")
       .setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2");
-
     action.createParam(COMPONENT_ROOTS)
       .setDeprecatedSince("5.1")
       .setDescription("If used, will have the same meaning as componentKeys AND onComponentOnly=false.");
@@ -273,9 +271,10 @@ public class SearchAction implements IssuesWsAction {
       .setExampleValue("7d8749e8-3070-4903-9188-bdd82933bb92");
 
     action.createParam(DIRECTORIES)
-      .setDescription("Since 5.1. To retrieve issues associated to a specific list of directories (comma-separated list of directory paths). " +
+      .setDescription("To retrieve issues associated to a specific list of directories (comma-separated list of directory paths). " +
         "This parameter is only meaningful when a module is selected. " +
         INTERNAL_PARAMETER_DISCLAIMER)
+      .setSince("5.1")
       .setExampleValue("src/main/java/org/sonar/server/");
 
     action.createParam(FILE_UUIDS)
@@ -362,12 +361,6 @@ public class SearchAction implements IssuesWsAction {
     addMandatoryValuesToFacet(facets, LANGUAGES, request.getLanguages());
     addMandatoryValuesToFacet(facets, TAGS, request.getTags());
     addMandatoryValuesToFacet(facets, TYPES, RuleType.names());
-    List<String> actionPlans = Lists.newArrayList("");
-    List<String> actionPlansFromRequest = request.getActionPlans();
-    if (actionPlansFromRequest != null) {
-      actionPlans.addAll(actionPlansFromRequest);
-    }
-    addMandatoryValuesToFacet(facets, DEPRECATED_ACTION_PLANS, actionPlans);
     addMandatoryValuesToFacet(facets, COMPONENT_UUIDS, request.getComponentUuids());
 
     for (String facetName : request.getFacets()) {
index 45e2a609ed744fdea61c35d059908294ae099662..a3fc80c56b241e4a4d2eba24c8381a2ea3168016 100644 (file)
@@ -24,7 +24,6 @@ import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Date;
 import java.util.Map;
 import org.assertj.core.api.Fail;
@@ -194,12 +193,10 @@ public class IssueQueryServiceTest {
     map.put("components", componentKeys);
     map.put("componentUuids", newArrayList("ABCD"));
 
-    try {
-      underTest.createFromMap(map);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("components and componentUuids cannot be set simultaneously");
-    }
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
+
+    underTest.createFromMap(map);
   }
 
   @Test
@@ -222,12 +219,10 @@ public class IssueQueryServiceTest {
     map.put("componentRoots", newArrayList("org.apache"));
     map.put("componentRootUuids", newArrayList("ABCD"));
 
-    try {
-      underTest.createFromMap(map);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("componentRoots and componentRootUuids cannot be set simultaneously");
-    }
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
+
+    underTest.createFromMap(map);
   }
 
   @Test
@@ -472,8 +467,7 @@ public class IssueQueryServiceTest {
 
     underTest.createFromRequest(new SearchWsRequest()
       .setSinceLeakPeriod(true)
-      .setComponentUuids(Collections.singletonList("component-uuid"))
-      .setProjectUuids(Collections.singletonList("project-uuid")));
+      .setComponentUuids(newArrayList("component-uuid", "project-uuid")));
   }
 
   @Test
index 23f995b45cd5acb8ea408dc5fa6c7021bd443fb7..85c181cc30c94decc26e8bfec35abf03c122ad69 100644 (file)
@@ -240,6 +240,7 @@ public class SearchActionComponentsMediumTest {
     tester.get(IssueIndexer.class).indexAll();
 
     wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION)
+      .setParam(IssueFilterParameters.COMPONENT_UUIDS, project.uuid())
       .setParam(IssueFilterParameters.FILE_UUIDS, file.uuid())
       .setParam(IssueFilterParameters.SINCE_LEAK_PERIOD, "true")
       .execute()
index e42473d03377e9d5595e3c168ff5ec5a6e7720bb..0e1eba6c76360ba008817cb11f540aa33f080368 100644 (file)
@@ -264,8 +264,19 @@ public interface WebService extends Definable<WebService.Context> {
       return this;
     }
 
-    public NewAction setDescription(@Nullable String s) {
-      this.description = s;
+    /**
+     * Used in Orchestrator
+     */
+    public NewAction setDescription(@Nullable String description) {
+      this.description = description;
+      return this;
+    }
+
+    /**
+     * @since 5.6
+     */
+    public NewAction setDescription(@Nullable String description, Object... descriptionArgument) {
+      this.description = description == null ? null : String.format(description, descriptionArgument);
       return this;
     }
 
@@ -274,6 +285,9 @@ public interface WebService extends Definable<WebService.Context> {
       return this;
     }
 
+    /**
+     * @since 5.3
+     */
     public NewAction setDeprecatedSince(@Nullable String deprecatedSince) {
       this.deprecatedSince = deprecatedSince;
       return this;
@@ -592,11 +606,17 @@ public interface WebService extends Definable<WebService.Context> {
       this.key = key;
     }
 
+    /**
+     * @since 5.3
+     */
     public NewParam setSince(@Nullable String since) {
       this.since = since;
       return this;
     }
 
+    /**
+     * @since 5.3
+     */
     public NewParam setDeprecatedSince(@Nullable String deprecatedSince) {
       this.deprecatedSince = deprecatedSince;
       return this;
@@ -610,8 +630,16 @@ public interface WebService extends Definable<WebService.Context> {
       return this;
     }
 
-    public NewParam setDescription(@Nullable String s) {
-      this.description = s;
+    public NewParam setDescription(@Nullable String description) {
+      this.description = description;
+      return this;
+    }
+
+    /**
+     * @since 5.6
+     */
+    public NewParam setDescription(@Nullable String description, Object... descriptionArgument) {
+      this.description = description == null ? null : String.format(description, descriptionArgument);
       return this;
     }