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
This commit is contained in:
Paladox 2019-02-08 14:42:30 +00:00
commit 520d92157a
9 changed files with 155 additions and 16 deletions

View File

@ -1359,7 +1359,7 @@ any of their groups is used.
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]]

View File

@ -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.common.WebLinkInfo;
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.ProjectCache;
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.List;
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 @@ public class ListProjects implements RestReadView<TopLevelResource> {
private String matchSubstring;
private String matchRegex;
private AccountGroup.UUID groupUuid;
private final Provider<QueryProjects> queryProjectsProvider;
@Inject
protected ListProjects(
@ -254,7 +263,8 @@ public class ListProjects implements RestReadView<TopLevelResource> {
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 @@ public class ListProjects implements RestReadView<TopLevelResource> {
this.permissionBackend = permissionBackend;
this.projectNodeFactory = projectNodeFactory;
this.webLinks = webLinks;
this.queryProjectsProvider = queryProjectsProvider;
}
public List<String> getShowBranch() {
@ -291,7 +302,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
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 class ListProjects implements RestReadView<TopLevelResource> {
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 @@ public class ListProjects implements RestReadView<TopLevelResource> {
}
}
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 @@ public class ListProjects implements RestReadView<TopLevelResource> {
}
if (showDescription) {
info.description = Strings.emptyToNull(e.getProject().getDescription());
info.description = emptyToNull(e.getProject().getDescription());
}
info.state = e.getProject().getState();

View File

@ -41,6 +41,6 @@ public class ListProjectsCommand extends SshCommand {
throw die("--tree and --description options are not compatible.");
}
}
impl.display(out);
impl.displayToStream(out);
}
}

View File

@ -128,7 +128,7 @@ public class ListProjectsIT extends AbstractDaemonTest {
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 @@ public class ListProjectsIT extends AbstractDaemonTest {
listProjects.setStart(numInitialProjects);
listProjects.setFormat(jsonFormat);
listProjects.display(displayOut);
listProjects.displayToStream(displayOut);
String projectsJsonOutput = new String(displayOut.toByteArray());

View File

@ -109,6 +109,7 @@ limitations under the License.
items="{{_rules}}"
as="rule">
<gr-rule-editor
has-range="[[_computeHasRange(name)]]"
label="[[_label]]"
editing="[[editing]]"
group-id="[[rule.id]]"

View File

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

View File

@ -255,6 +255,14 @@ limitations under the License.
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', () => {

View File

@ -71,7 +71,11 @@ limitations under the License.
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 @@ limitations under the License.
</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>

View File

@ -67,6 +67,7 @@
is: 'gr-rule-editor',
properties: {
hasRange: Boolean,
/** @type {?} */
label: Object,
editing: {