ListPlugins: Add support for filtering with prefix/substring/regex
Feature: Issue 6499 Change-Id: I7e54595dad56fc6a5e34bea55adee341077ad1fa
This commit is contained in:
		@@ -110,6 +110,74 @@ Query the first plugin in the plugin list:
 | 
			
		||||
  }
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
Prefix(p)::
 | 
			
		||||
Limit the results to those plugins that start with the specified
 | 
			
		||||
prefix.
 | 
			
		||||
+
 | 
			
		||||
The match is case sensitive. May not be used together with `m` or `r`.
 | 
			
		||||
+
 | 
			
		||||
List all plugins that start with `delete`:
 | 
			
		||||
+
 | 
			
		||||
.Request
 | 
			
		||||
----
 | 
			
		||||
  GET /plugins/?p=delete HTTP/1.0
 | 
			
		||||
----
 | 
			
		||||
+
 | 
			
		||||
.Response
 | 
			
		||||
----
 | 
			
		||||
  HTTP/1.1 200 OK
 | 
			
		||||
  Content-Disposition: attachment
 | 
			
		||||
  Content-Type: application/json; charset=UTF-8
 | 
			
		||||
 | 
			
		||||
  )]}'
 | 
			
		||||
  {
 | 
			
		||||
    "delete-project": {
 | 
			
		||||
      "id": "delete-project",
 | 
			
		||||
      "index_url": "plugins/delete-project/",
 | 
			
		||||
      "version": "2.9-SNAPSHOT"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
----
 | 
			
		||||
+
 | 
			
		||||
E.g. this feature can be used by suggestion client UI's to limit results.
 | 
			
		||||
 | 
			
		||||
Regex(r)::
 | 
			
		||||
Limit the results to those plugins that match the specified regex.
 | 
			
		||||
+
 | 
			
		||||
Boundary matchers '^' and '$' are implicit. For example: the regex 'test.*' will
 | 
			
		||||
match any plugins that start with 'test' and regex '.*test' will match any
 | 
			
		||||
project that end with 'test'.
 | 
			
		||||
+
 | 
			
		||||
The match is case sensitive. May not be used together with `m` or `p`.
 | 
			
		||||
+
 | 
			
		||||
List all plugins that match regex `some.*plugin`:
 | 
			
		||||
+
 | 
			
		||||
.Request
 | 
			
		||||
----
 | 
			
		||||
  GET /plugins/?r=some.*plugin HTTP/1.0
 | 
			
		||||
----
 | 
			
		||||
+
 | 
			
		||||
.Response
 | 
			
		||||
----
 | 
			
		||||
  HTTP/1.1 200 OK
 | 
			
		||||
  Content-Disposition: attachment
 | 
			
		||||
  Content-Type: application/json; charset=UTF-8
 | 
			
		||||
 | 
			
		||||
  )]}'
 | 
			
		||||
  {
 | 
			
		||||
    "some-plugin": {
 | 
			
		||||
      "id": "some-plugin",
 | 
			
		||||
      "index_url": "plugins/some-plugin/",
 | 
			
		||||
      "version": "2.9-SNAPSHOT"
 | 
			
		||||
    },
 | 
			
		||||
    "some-other-plugin": {
 | 
			
		||||
      "id": "some-other-plugin",
 | 
			
		||||
      "index_url": "plugins/some-other-plugin/",
 | 
			
		||||
      "version": "2.9-SNAPSHOT"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
Skip(S)::
 | 
			
		||||
Skip the given number of plugins from the beginning of the list.
 | 
			
		||||
@@ -138,6 +206,33 @@ Query the second plugin in the plugin list:
 | 
			
		||||
  }
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
Substring(m)::
 | 
			
		||||
Limit the results to those plugins that match the specified substring.
 | 
			
		||||
+
 | 
			
		||||
The match is case insensitive. May not be used together with `r` or `p`.
 | 
			
		||||
+
 | 
			
		||||
List all plugins that match substring `project`:
 | 
			
		||||
+
 | 
			
		||||
.Request
 | 
			
		||||
----
 | 
			
		||||
  GET /plugins/?m=project HTTP/1.0
 | 
			
		||||
----
 | 
			
		||||
+
 | 
			
		||||
.Response
 | 
			
		||||
----
 | 
			
		||||
  HTTP/1.1 200 OK
 | 
			
		||||
  Content-Disposition: attachment
 | 
			
		||||
  Content-Type: application/json; charset=UTF-8
 | 
			
		||||
 | 
			
		||||
  )]}'
 | 
			
		||||
  {
 | 
			
		||||
    "delete-project": {
 | 
			
		||||
      "id": "delete-project",
 | 
			
		||||
      "index_url": "plugins/delete-project/",
 | 
			
		||||
      "version": "2.9-SNAPSHOT"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
[[install-plugin]]
 | 
			
		||||
=== Install Plugin
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ package com.google.gerrit.acceptance.api.plugin;
 | 
			
		||||
import static com.google.common.truth.Truth.assertThat;
 | 
			
		||||
import static java.nio.charset.StandardCharsets.UTF_8;
 | 
			
		||||
import static java.util.stream.Collectors.toList;
 | 
			
		||||
import static org.junit.Assert.fail;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
 | 
			
		||||
@@ -27,6 +28,7 @@ import com.google.gerrit.extensions.api.plugins.PluginApi;
 | 
			
		||||
import com.google.gerrit.extensions.api.plugins.Plugins.ListRequest;
 | 
			
		||||
import com.google.gerrit.extensions.common.InstallPluginInput;
 | 
			
		||||
import com.google.gerrit.extensions.common.PluginInfo;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.BadRequestException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.RestApiException;
 | 
			
		||||
@@ -63,6 +65,24 @@ public class PluginIT extends AbstractDaemonTest {
 | 
			
		||||
    // With pagination
 | 
			
		||||
    assertPlugins(list().start(1).limit(2).get(), PLUGINS.subList(1, 3));
 | 
			
		||||
 | 
			
		||||
    // With prefix
 | 
			
		||||
    assertPlugins(list().prefix("plugin-b").get(), ImmutableList.of("plugin-b"));
 | 
			
		||||
    assertPlugins(list().prefix("PLUGIN-").get(), ImmutableList.of());
 | 
			
		||||
 | 
			
		||||
    // With substring
 | 
			
		||||
    assertPlugins(list().substring("lugin-").get(), PLUGINS);
 | 
			
		||||
    assertPlugins(list().substring("lugin-").start(1).limit(2).get(), PLUGINS.subList(1, 3));
 | 
			
		||||
 | 
			
		||||
    // With regex
 | 
			
		||||
    assertPlugins(list().regex(".*in-b").get(), ImmutableList.of("plugin-b"));
 | 
			
		||||
    assertPlugins(list().regex("plugin-.*").get(), PLUGINS);
 | 
			
		||||
    assertPlugins(list().regex("plugin-.*").start(1).limit(2).get(), PLUGINS.subList(1, 3));
 | 
			
		||||
 | 
			
		||||
    // Invalid match combinations
 | 
			
		||||
    assertBadRequest(list().regex(".*in-b").substring("a"));
 | 
			
		||||
    assertBadRequest(list().regex(".*in-b").prefix("a"));
 | 
			
		||||
    assertBadRequest(list().substring(".*in-b").prefix("a"));
 | 
			
		||||
 | 
			
		||||
    // Disable
 | 
			
		||||
    api = gApi.plugins().name("plugin-a");
 | 
			
		||||
    api.disable();
 | 
			
		||||
@@ -99,4 +119,13 @@ public class PluginIT extends AbstractDaemonTest {
 | 
			
		||||
    List<String> _actual = actual.stream().map(p -> p.id).collect(toList());
 | 
			
		||||
    assertThat(_actual).containsExactlyElementsIn(expected);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void assertBadRequest(ListRequest req) throws Exception {
 | 
			
		||||
    try {
 | 
			
		||||
      req.get();
 | 
			
		||||
      fail("Expected BadRequestException");
 | 
			
		||||
    } catch (BadRequestException e) {
 | 
			
		||||
      // Expected
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,9 @@ public interface Plugins {
 | 
			
		||||
    private boolean all;
 | 
			
		||||
    private int limit;
 | 
			
		||||
    private int start;
 | 
			
		||||
    private String substring;
 | 
			
		||||
    private String prefix;
 | 
			
		||||
    private String regex;
 | 
			
		||||
 | 
			
		||||
    public List<PluginInfo> get() throws RestApiException {
 | 
			
		||||
      Map<String, PluginInfo> map = getAsMap();
 | 
			
		||||
@@ -73,6 +76,33 @@ public interface Plugins {
 | 
			
		||||
    public int getStart() {
 | 
			
		||||
      return start;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ListRequest substring(String substring) {
 | 
			
		||||
      this.substring = substring;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getSubstring() {
 | 
			
		||||
      return substring;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ListRequest prefix(String prefix) {
 | 
			
		||||
      this.prefix = prefix;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getPrefix() {
 | 
			
		||||
      return prefix;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ListRequest regex(String regex) {
 | 
			
		||||
      this.regex = regex;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getRegex() {
 | 
			
		||||
      return regex;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,9 @@ public class PluginsImpl implements Plugins {
 | 
			
		||||
        list.setAll(this.getAll());
 | 
			
		||||
        list.setStart(this.getStart());
 | 
			
		||||
        list.setLimit(this.getLimit());
 | 
			
		||||
        list.setMatchPrefix(this.getPrefix());
 | 
			
		||||
        list.setMatchSubstring(this.getSubstring());
 | 
			
		||||
        list.setMatchRegex(this.getRegex());
 | 
			
		||||
        return list.apply();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import com.google.gerrit.common.Nullable;
 | 
			
		||||
import com.google.gerrit.common.data.GlobalCapability;
 | 
			
		||||
import com.google.gerrit.extensions.annotations.RequiresCapability;
 | 
			
		||||
import com.google.gerrit.extensions.common.PluginInfo;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.BadRequestException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.RestReadView;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.TopLevelResource;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.Url;
 | 
			
		||||
@@ -31,9 +32,11 @@ import com.google.gson.reflect.TypeToken;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import java.io.PrintWriter;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.SortedMap;
 | 
			
		||||
import java.util.TreeMap;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
import org.kohsuke.args4j.Option;
 | 
			
		||||
 | 
			
		||||
@@ -45,6 +48,9 @@ public class ListPlugins implements RestReadView<TopLevelResource> {
 | 
			
		||||
  private boolean all;
 | 
			
		||||
  private int limit;
 | 
			
		||||
  private int start;
 | 
			
		||||
  private String matchPrefix;
 | 
			
		||||
  private String matchSubstring;
 | 
			
		||||
  private String matchRegex;
 | 
			
		||||
 | 
			
		||||
  @Deprecated
 | 
			
		||||
  @Option(name = "--format", usage = "(deprecated) output format")
 | 
			
		||||
@@ -79,6 +85,31 @@ public class ListPlugins implements RestReadView<TopLevelResource> {
 | 
			
		||||
    this.start = start;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Option(
 | 
			
		||||
    name = "--prefix",
 | 
			
		||||
    aliases = {"-p"},
 | 
			
		||||
    metaVar = "PREFIX",
 | 
			
		||||
    usage = "match plugin prefix"
 | 
			
		||||
  )
 | 
			
		||||
  public void setMatchPrefix(String matchPrefix) {
 | 
			
		||||
    this.matchPrefix = matchPrefix;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Option(
 | 
			
		||||
    name = "--match",
 | 
			
		||||
    aliases = {"-m"},
 | 
			
		||||
    metaVar = "MATCH",
 | 
			
		||||
    usage = "match plugin substring"
 | 
			
		||||
  )
 | 
			
		||||
  public void setMatchSubstring(String matchSubstring) {
 | 
			
		||||
    this.matchSubstring = matchSubstring;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Option(name = "-r", metaVar = "REGEX", usage = "match plugin regex")
 | 
			
		||||
  public void setMatchRegex(String matchRegex) {
 | 
			
		||||
    this.matchRegex = matchRegex;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  protected ListPlugins(PluginLoader pluginLoader) {
 | 
			
		||||
    this.pluginLoader = pluginLoader;
 | 
			
		||||
@@ -94,20 +125,33 @@ public class ListPlugins implements RestReadView<TopLevelResource> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Object apply(TopLevelResource resource) {
 | 
			
		||||
  public Object apply(TopLevelResource resource) throws BadRequestException {
 | 
			
		||||
    format = OutputFormat.JSON;
 | 
			
		||||
    return display(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public SortedMap<String, PluginInfo> apply() {
 | 
			
		||||
  public SortedMap<String, PluginInfo> apply() throws BadRequestException {
 | 
			
		||||
    format = OutputFormat.JSON;
 | 
			
		||||
    return display(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public SortedMap<String, PluginInfo> display(@Nullable PrintWriter stdout) {
 | 
			
		||||
  public SortedMap<String, PluginInfo> display(@Nullable PrintWriter stdout)
 | 
			
		||||
      throws BadRequestException {
 | 
			
		||||
    SortedMap<String, PluginInfo> output = new TreeMap<>();
 | 
			
		||||
    Stream<Plugin> s =
 | 
			
		||||
        Streams.stream(pluginLoader.getPlugins(all)).sorted(comparing(Plugin::getName));
 | 
			
		||||
    Stream<Plugin> s = Streams.stream(pluginLoader.getPlugins(all));
 | 
			
		||||
    if (matchPrefix != null) {
 | 
			
		||||
      checkMatchOptions(matchSubstring == null && matchRegex == null);
 | 
			
		||||
      s = s.filter(p -> p.getName().startsWith(matchPrefix));
 | 
			
		||||
    } else if (matchSubstring != null) {
 | 
			
		||||
      checkMatchOptions(matchPrefix == null && matchRegex == null);
 | 
			
		||||
      String substring = matchSubstring.toLowerCase(Locale.US);
 | 
			
		||||
      s = s.filter(p -> p.getName().toLowerCase(Locale.US).contains(substring));
 | 
			
		||||
    } else if (matchRegex != null) {
 | 
			
		||||
      checkMatchOptions(matchPrefix == null && matchSubstring == null);
 | 
			
		||||
      Pattern pattern = Pattern.compile(matchRegex);
 | 
			
		||||
      s = s.filter(p -> pattern.matcher(p.getName()).matches());
 | 
			
		||||
    }
 | 
			
		||||
    s = s.sorted(comparing(Plugin::getName));
 | 
			
		||||
    if (start > 0) {
 | 
			
		||||
      s = s.skip(start);
 | 
			
		||||
    }
 | 
			
		||||
@@ -148,6 +192,12 @@ public class ListPlugins implements RestReadView<TopLevelResource> {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void checkMatchOptions(boolean cond) throws BadRequestException {
 | 
			
		||||
    if (!cond) {
 | 
			
		||||
      throw new BadRequestException("specify exactly one of p/m/r");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static PluginInfo toPluginInfo(Plugin p) {
 | 
			
		||||
    String id;
 | 
			
		||||
    String version;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user