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:
commit
520d92157a
@ -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]]
|
||||
|
@ -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();
|
||||
|
||||
|
@ -41,6 +41,6 @@ public class ListProjectsCommand extends SshCommand {
|
||||
throw die("--tree and --description options are not compatible.");
|
||||
}
|
||||
}
|
||||
impl.display(out);
|
||||
impl.displayToStream(out);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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]]"
|
||||
|
@ -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());
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
@ -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', () => {
|
||||
|
@ -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>
|
||||
|
@ -67,6 +67,7 @@
|
||||
is: 'gr-rule-editor',
|
||||
|
||||
properties: {
|
||||
hasRange: Boolean,
|
||||
/** @type {?} */
|
||||
label: Object,
|
||||
editing: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user