Add support for regex in project list screen

Add support for regex in project list screen filter. If filter start
with '^', then the filter will be interpreted as a regex.

Feature: Issue 2751
Change-Id: I1cf8a0f9fc18a6e286a2a26c71be77f1d4f16741
This commit is contained in:
Hugo Arès 2014-07-08 11:04:43 -04:00
parent 91b7f7ce39
commit b8aae411a8
4 changed files with 99 additions and 5 deletions

View File

@ -175,6 +175,38 @@ List all projects that start with `platform/`:
+
E.g. this feature can be used by suggestion client UI's to limit results.
Regex(r)::
Limit the results to those projects that match the specified regex.
+
Boundary matchers '^' and '$' are implicit. For example: the regex 'test.*' will
match any projects that start with 'test' and regex '.*test' will match any
project that end with 'test'.
+
List all projects that match regex `test.*project`:
+
.Request
----
GET /projects/?r=test.*project HTTP/1.0
----
+
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
{
"test/some-project": {
"id": "test%2Fsome-project",
},
"test/some-other-project": {
"id": "test%2Fsome-other-project",
}
}
----
Skip(S)::
Skip the given number of projects from the beginning of the list.
+

View File

@ -128,6 +128,31 @@ public class ListProjectsIT extends AbstractDaemonTest {
result.values());
}
@Test
public void listProjectsWithRegex() throws IOException, JSchException {
Project.NameKey someProject = new Project.NameKey("some-project");
createProject(sshSession, someProject.get());
Project.NameKey someOtherProject =
new Project.NameKey("some-other-project");
createProject(sshSession, someOtherProject.get());
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
RestResponse r = GET("/projects/?r=.*some");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
Map<String, ProjectInfo> result = toProjectInfoMap(r);
assertProjects(Arrays.asList(projectAwesome), result.values());
r = GET("/projects/?r=[.*some");
assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
r = GET("/projects/?r=.*");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
result = toProjectInfoMap(r);
assertProjects(Arrays.asList(someProject, someOtherProject, projectAwesome,
project, allUsers), result.values());
}
@Test
public void listProjectsWithSkip() throws IOException, JSchException {
for (int i = 0; i < 5; i++) {

View File

@ -56,7 +56,11 @@ public class ProjectMap extends NativeMap<ProjectInfo> {
public static void match(String match, int limit, int start, AsyncCallback<ProjectMap> cb) {
RestApi call = new RestApi("/projects/");
if (match != null) {
call.addParameter("m", match);
if (match.startsWith("^")) {
call.addParameter("r", match);
} else {
call.addParameter("m", match);
}
}
if (limit > 0) {
call.addParameter("n", limit);

View File

@ -23,6 +23,7 @@ import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@ -42,6 +43,9 @@ import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Provider;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
@ -163,6 +167,11 @@ public class ListProjects implements RestReadView<TopLevelResource> {
this.matchSubstring = matchSubstring;
}
@Option(name = "-r", metaVar = "REGEX", usage = "match project regex")
public void setMatchRegex(String matchRegex) {
this.matchRegex = matchRegex;
}
@Option(name = "--has-acl-for", metaVar = "GROUP", usage =
"displays only projects on which access rights for this group are directly assigned")
public void setGroupUuid(AccountGroup.UUID groupUuid) {
@ -178,6 +187,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
private int start;
private String matchPrefix;
private String matchSubstring;
private String matchRegex;
private AccountGroup.UUID groupUuid;
@Inject
@ -216,7 +226,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
}
@Override
public Object apply(TopLevelResource resource) {
public Object apply(TopLevelResource resource) throws BadRequestException {
if (format == OutputFormat.TEXT) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
display(buf);
@ -227,12 +237,13 @@ public class ListProjects implements RestReadView<TopLevelResource> {
return apply();
}
public Map<String, ProjectInfo> apply() {
public Map<String, ProjectInfo> apply() throws BadRequestException {
format = OutputFormat.JSON;
return display(null);
}
public Map<String, ProjectInfo> display(OutputStream displayOutputStream) {
public Map<String, ProjectInfo> display(OutputStream displayOutputStream)
throws BadRequestException {
PrintWriter stdout = null;
if (displayOutputStream != null) {
try {
@ -436,7 +447,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
}
}
private Iterable<Project.NameKey> scan() {
private Iterable<Project.NameKey> scan() throws BadRequestException {
if (matchPrefix != null) {
return projectCache.byName(matchPrefix);
} else if (matchSubstring != null) {
@ -447,6 +458,28 @@ public class ListProjects implements RestReadView<TopLevelResource> {
.contains(matchSubstring.toLowerCase(Locale.US));
}
});
} else if (matchRegex != null) {
if (matchRegex.startsWith("^")) {
matchRegex = matchRegex.substring(1);
if (matchRegex.endsWith("$") && !matchRegex.endsWith("\\$")) {
matchRegex = matchRegex.substring(0, matchRegex.length() - 1);
}
}
if (matchRegex.equals(".*")) {
return projectCache.all();
}
try {
final RunAutomaton a =
new RunAutomaton(new RegExp(matchRegex).toAutomaton());
return Iterables.filter(projectCache.all(),
new Predicate<Project.NameKey>() {
public boolean apply(Project.NameKey in) {
return a.run(in.get());
}
});
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage());
}
} else {
return projectCache.all();
}