Merge branch 'stable-2.16'
* stable-2.16:
Support for setting max/min value for "Query Limit" and "Batch Changes"
Mark DeletePrivate input as @Nullable
ListProjects: print projects list using secondary index
ListProjects: re-implement using secondary index
Fix replacing ${project-base-name} in gr-repo
Change-Id: Id9123dcfbd123a529612fccaecd64ee0c196b216
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index dfd215e..2e7cf17 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1359,7 +1359,7 @@
This limit applies not only to the link:cmd-query.html[`gerrit query`]
command, but also to the web UI results pagination size in the new
-PolyGerrit UI.
+PolyGerrit UI and, limited to the full project list, in the old GWT UI.
[[capability_readAs]]
diff --git a/java/com/google/gerrit/server/restapi/project/ListProjects.java b/java/com/google/gerrit/server/restapi/project/ListProjects.java
index 35d1ca1..1600fed 100644
--- a/java/com/google/gerrit/server/restapi/project/ListProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/ListProjects.java
@@ -14,11 +14,15 @@
package com.google.gerrit.server.restapi.project;
+import static com.google.common.base.Strings.emptyToNull;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.common.collect.Ordering.natural;
import static com.google.gerrit.extensions.client.ProjectState.HIDDEN;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.common.base.Strings;
+import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
@@ -29,6 +33,7 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
@@ -51,7 +56,9 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -67,6 +74,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
@@ -244,6 +252,7 @@
private String matchSubstring;
private String matchRegex;
private AccountGroup.UUID groupUuid;
+ private final Provider<QueryProjects> queryProjectsProvider;
@Inject
protected ListProjects(
@@ -254,7 +263,8 @@
GitRepositoryManager repoManager,
PermissionBackend permissionBackend,
ProjectNode.Factory projectNodeFactory,
- WebLinks webLinks) {
+ WebLinks webLinks,
+ Provider<QueryProjects> queryProjectsProvider) {
this.currentUser = currentUser;
this.projectCache = projectCache;
this.groupResolver = groupResolver;
@@ -263,6 +273,7 @@
this.permissionBackend = permissionBackend;
this.projectNodeFactory = projectNodeFactory;
this.webLinks = webLinks;
+ this.queryProjectsProvider = queryProjectsProvider;
}
public List<String> getShowBranch() {
@@ -291,7 +302,7 @@
throws BadRequestException, PermissionBackendException {
if (format == OutputFormat.TEXT) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
- display(buf);
+ displayToStream(buf);
return BinaryResult.create(buf.toByteArray())
.setContentType("text/plain")
.setCharacterEncoding(UTF_8);
@@ -301,11 +312,104 @@
public SortedMap<String, ProjectInfo> apply()
throws BadRequestException, PermissionBackendException {
+ Optional<String> projectQuery = expressAsProjectsQuery();
+ if (projectQuery.isPresent()) {
+ return applyAsQuery(projectQuery.get());
+ }
+
format = OutputFormat.JSON;
return display(null);
}
- public SortedMap<String, ProjectInfo> display(@Nullable OutputStream displayOutputStream)
+ private Optional<String> expressAsProjectsQuery() {
+ return !all
+ && state != HIDDEN
+ && isNullOrEmpty(matchPrefix)
+ && isNullOrEmpty(matchRegex)
+ && isNullOrEmpty(matchSubstring) // TODO: see Issue 10446
+ && type == FilterType.ALL
+ && showBranch.isEmpty()
+ && !showTree
+ ? Optional.of(stateToQuery())
+ : Optional.empty();
+ }
+
+ private String stateToQuery() {
+ List<String> queries = new ArrayList<>();
+ if (state == null) {
+ queries.add("(state:active OR state:read-only)");
+ } else {
+ queries.add(String.format("(state:%s)", state.name()));
+ }
+
+ return Joiner.on(" AND ").join(queries).toString();
+ }
+
+ private SortedMap<String, ProjectInfo> applyAsQuery(String query) throws BadRequestException {
+ try {
+ return queryProjectsProvider
+ .get()
+ .withQuery(query)
+ .withStart(start)
+ .withLimit(limit)
+ .apply()
+ .stream()
+ .collect(
+ ImmutableSortedMap.toImmutableSortedMap(
+ natural(), p -> p.name, p -> showDescription ? p : nullifyDescription(p)));
+ } catch (OrmException | MethodNotAllowedException e) {
+ logger.atWarning().withCause(e).log(
+ "Internal error while processing the query '{}' request", query);
+ throw new BadRequestException("Internal error while processing the query request");
+ }
+ }
+
+ private ProjectInfo nullifyDescription(ProjectInfo p) {
+ p.description = null;
+ return p;
+ }
+
+ private void printQueryResults(String query, PrintWriter out) throws BadRequestException {
+ try {
+ if (format.isJson()) {
+ format.newGson().toJson(applyAsQuery(query), out);
+ } else {
+ newProjectsNamesStream(query).forEach(out::println);
+ }
+ out.flush();
+ } catch (OrmException | MethodNotAllowedException e) {
+ logger.atWarning().withCause(e).log(
+ "Internal error while processing the query '{}' request", query);
+ throw new BadRequestException("Internal error while processing the query request");
+ }
+ }
+
+ private Stream<String> newProjectsNamesStream(String query)
+ throws OrmException, MethodNotAllowedException, BadRequestException {
+ Stream<String> projects =
+ queryProjectsProvider.get().withQuery(query).apply().stream().map(p -> p.name).skip(start);
+ if (limit > 0) {
+ projects = projects.limit(limit);
+ }
+
+ return projects;
+ }
+
+ public void displayToStream(OutputStream displayOutputStream)
+ throws BadRequestException, PermissionBackendException {
+ PrintWriter stdout =
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(displayOutputStream, UTF_8)));
+ Optional<String> projectsQuery = expressAsProjectsQuery();
+
+ if (projectsQuery.isPresent()) {
+ printQueryResults(projectsQuery.get(), stdout);
+ } else {
+ display(stdout);
+ }
+ }
+
+ @Nullable
+ public SortedMap<String, ProjectInfo> display(@Nullable PrintWriter stdout)
throws BadRequestException, PermissionBackendException {
if (all && state != null) {
throw new BadRequestException("'all' and 'state' may not be used together");
@@ -320,12 +424,6 @@
}
}
- PrintWriter stdout = null;
- if (displayOutputStream != null) {
- stdout =
- new PrintWriter(new BufferedWriter(new OutputStreamWriter(displayOutputStream, UTF_8)));
- }
-
int foundIndex = 0;
int found = 0;
TreeMap<String, ProjectInfo> output = new TreeMap<>();
@@ -373,7 +471,7 @@
}
if (showDescription) {
- info.description = Strings.emptyToNull(e.getProject().getDescription());
+ info.description = emptyToNull(e.getProject().getDescription());
}
info.state = e.getProject().getState();
diff --git a/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java b/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
index d04e2d3..9f2ffa9 100644
--- a/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
@@ -41,6 +41,6 @@
throw die("--tree and --description options are not compatible.");
}
}
- impl.display(out);
+ impl.displayToStream(out);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
index 178d2bf..a30e2b9 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
@@ -128,7 +128,7 @@
try (ByteArrayOutputStream displayOut = new ByteArrayOutputStream()) {
listProjects.setStart(numInitialProjects);
- listProjects.display(displayOut);
+ listProjects.displayToStream(displayOut);
List<String> lines =
Splitter.on("\n").omitEmptyStrings().splitToList(new String(displayOut.toByteArray()));
@@ -158,7 +158,7 @@
listProjects.setStart(numInitialProjects);
listProjects.setFormat(jsonFormat);
- listProjects.display(displayOut);
+ listProjects.displayToStream(displayOut);
String projectsJsonOutput = new String(displayOut.toByteArray());
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
index 22f461b..a5bb5fd 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
@@ -109,6 +109,7 @@
items="{{_rules}}"
as="rule">
<gr-rule-editor
+ has-range="[[_computeHasRange(name)]]"
label="[[_label]]"
editing="[[editing]]"
group-id="[[rule.id]]"
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
index 31d371d..afe5a86 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -19,6 +19,11 @@
const MAX_AUTOCOMPLETE_RESULTS = 20;
+ const RANGE_NAMES = [
+ 'QUERY LIMIT',
+ 'BATCH CHANGES LIMIT',
+ ];
+
/**
* Fired when the permission has been modified or removed.
*
@@ -269,5 +274,11 @@
this.set(['permission', 'value', 'rules', groupId], value);
this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
},
+
+ _computeHasRange(name) {
+ if (!name) { return false; }
+
+ return RANGE_NAMES.includes(name.toUpperCase());
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index e29c4a2..a6381d1 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -255,6 +255,14 @@
assert.isFalse(element._deleted);
assert.isNotOk(element.permission.value.deleted);
});
+
+ test('_computeHasRange', () => {
+ assert.isTrue(element._computeHasRange('Query Limit'));
+
+ assert.isTrue(element._computeHasRange('Batch Changes Limit'));
+
+ assert.isFalse(element._computeHasRange('test'));
+ });
});
suite('interactions', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html
index d59deed4..c8ae650 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html
@@ -71,7 +71,11 @@
color: var(--deemphasized-text-color);
}
</style>
- <style include="gr-form-styles"></style>
+ <style include="gr-form-styles">
+ iron-autogrow-textarea {
+ width: 14em;
+ }
+ </style>
<div id="mainContainer"
class$="gr-form-styles [[_computeSectionClass(editing, _deleted)]]">
<div id="options">
@@ -106,6 +110,22 @@
</select>
</gr-select>
</template>
+ <template is="dom-if" if="[[hasRange]]">
+ <iron-autogrow-textarea
+ id="minInput"
+ class="min"
+ autocomplete="on"
+ placeholder="Min value"
+ bind-value="{{rule.value.min}}"
+ disabled$="[[!editing]]"></iron-autogrow-textarea>
+ <iron-autogrow-textarea
+ id="maxInput"
+ class="max"
+ autocomplete="on"
+ placeholder="Max value"
+ bind-value="{{rule.value.max}}"
+ disabled$="[[!editing]]"></iron-autogrow-textarea>
+ </template>
<a class="groupPath" href$="[[_computeGroupPath(groupId)]]">
[[groupName]]
</a>
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
index b99125c..06f703f 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
@@ -67,6 +67,7 @@
is: 'gr-rule-editor',
properties: {
+ hasRange: Boolean,
/** @type {?} */
label: Object,
editing: {