From b8aae411a878bc16ddb004b328e7a2e827e9a8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Ar=C3=A8s?= Date: Tue, 8 Jul 2014 11:04:43 -0400 Subject: [PATCH] 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 --- Documentation/rest-api-projects.txt | 32 +++++++++++++++ .../rest/project/ListProjectsIT.java | 25 +++++++++++ .../gerrit/client/projects/ProjectMap.java | 6 ++- .../gerrit/server/project/ListProjects.java | 41 +++++++++++++++++-- 4 files changed, 99 insertions(+), 5 deletions(-) diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt index 3a36cbe85c..4d9e02c4ef 100644 --- a/Documentation/rest-api-projects.txt +++ b/Documentation/rest-api-projects.txt @@ -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. + diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java index 953a21a368..923d752bdf 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java @@ -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 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++) { diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java index f262660450..29a8a019b2 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java @@ -56,7 +56,11 @@ public class ProjectMap extends NativeMap { public static void match(String match, int limit, int start, AsyncCallback 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); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java index 5153c06499..b5f711f251 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java @@ -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 { 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 { 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 { } @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 { return apply(); } - public Map apply() { + public Map apply() throws BadRequestException { format = OutputFormat.JSON; return display(null); } - public Map display(OutputStream displayOutputStream) { + public Map display(OutputStream displayOutputStream) + throws BadRequestException { PrintWriter stdout = null; if (displayOutputStream != null) { try { @@ -436,7 +447,7 @@ public class ListProjects implements RestReadView { } } - private Iterable scan() { + private Iterable scan() throws BadRequestException { if (matchPrefix != null) { return projectCache.byName(matchPrefix); } else if (matchSubstring != null) { @@ -447,6 +458,28 @@ public class ListProjects implements RestReadView { .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() { + public boolean apply(Project.NameKey in) { + return a.run(in.get()); + } + }); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.getMessage()); + } } else { return projectCache.all(); }