ListPlugins: Add support for filtering with prefix/substring/regex

Feature: Issue 6499
Change-Id: I7e54595dad56fc6a5e34bea55adee341077ad1fa
This commit is contained in:
David Pursehouse 2017-07-27 22:14:38 +01:00
parent 7f577a908c
commit 68d975b496
5 changed files with 212 additions and 5 deletions

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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;
}
}
/**

View File

@ -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();
}
};

View File

@ -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;