Add custom user named query support
This parses user queries from the "queries" file in the user's ref in the All-Users project. The "queries" file format is a simple text file with two tab separated columns: NAME and QUERY. The named queries are accessible via the new query:<name> operator. Change-Id: I1dd12230fe7488164ae1711c6c2fd22ebcd14166
This commit is contained in:
parent
b6b6ee280a
commit
5e5a36f736
32
Documentation/user-named-queries.txt
Normal file
32
Documentation/user-named-queries.txt
Normal file
@ -0,0 +1,32 @@
|
||||
= Gerrit Code Review - Named Queries
|
||||
|
||||
[[user-named-queries]]
|
||||
== User Named Queries
|
||||
It is possible to define named queries on a user level. To do
|
||||
this, define the named queries in the `queries` file of
|
||||
the user's account ref in the `All-Users` project. The user's
|
||||
account ref is based on the user's account id which is an
|
||||
integer. The account refs are sharded by the last two digits
|
||||
(`+nn+`) in the refname, leading to refs of the format
|
||||
`+refs/users/nn/accountid+`. The user's queries file is a
|
||||
2 column tab delimited file. The left column represents the
|
||||
name of the query, and the right column represents the query
|
||||
expression represented by the name.
|
||||
|
||||
Example queries file:
|
||||
|
||||
----
|
||||
# Name Query
|
||||
#
|
||||
selfapproved owner:self label:code-review+2,user=self
|
||||
blocked label:code-review-2 OR label:verified-1
|
||||
# Note below how to reference your own named queries in other named queries
|
||||
ready label:code-review+2 label:verified+1 -query:blocked status:open
|
||||
----
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
||||
|
||||
SEARCHBOX
|
||||
---------
|
@ -98,6 +98,12 @@ ownerin:'GROUP'::
|
||||
+
|
||||
Changes originally submitted by a user in 'GROUP'.
|
||||
|
||||
[[query]]
|
||||
query:'NAME'::
|
||||
+
|
||||
Changes which match the current user's query named 'NAME'
|
||||
(see link:user-named-queries.html[Named Queries]).
|
||||
|
||||
[[reviewer]]
|
||||
reviewer:'USER', r:'USER'::
|
||||
+
|
||||
|
@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2015 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.server.git.QueryList;
|
||||
import com.google.gerrit.server.git.ValidationError;
|
||||
import com.google.gerrit.server.git.VersionedMetaData;
|
||||
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.lib.CommitBuilder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** Named Queries for user accounts. */
|
||||
public class VersionedAccountQueries extends VersionedMetaData {
|
||||
private static final Logger log = LoggerFactory.getLogger(VersionedAccountQueries.class);
|
||||
|
||||
public static VersionedAccountQueries forUser(Account.Id id) {
|
||||
return new VersionedAccountQueries(RefNames.refsUsers(id));
|
||||
}
|
||||
|
||||
private final String ref;
|
||||
private QueryList queryList;
|
||||
|
||||
private VersionedAccountQueries(String ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRefName() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
public QueryList getQueryList() {
|
||||
return queryList;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoad() throws IOException, ConfigInvalidException {
|
||||
ValidationError.Sink errors = new ValidationError.Sink() {
|
||||
@Override
|
||||
public void error(ValidationError error) {
|
||||
log.error("Error parsing file " + QueryList.FILE_NAME + ": " +
|
||||
error.getMessage());
|
||||
}
|
||||
};
|
||||
queryList = QueryList.parse(readUTF8(QueryList.FILE_NAME), errors);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onSave(CommitBuilder commit) throws IOException,
|
||||
ConfigInvalidException {
|
||||
throw new UnsupportedOperationException("Cannot yet save named queries");
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// Copyright (C) 2015 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.git;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class QueryList extends TabFile {
|
||||
public static final String FILE_NAME = "queries";
|
||||
protected final Map<String, String> queriesByName;
|
||||
|
||||
private QueryList(List<Row> queriesByName) {
|
||||
this.queriesByName = toMap(queriesByName);
|
||||
}
|
||||
|
||||
public static QueryList parse(String text, ValidationError.Sink errors)
|
||||
throws IOException {
|
||||
return new QueryList(parse(text, FILE_NAME, errors));
|
||||
}
|
||||
|
||||
public String getQuery(String name) {
|
||||
return queriesByName.get(name);
|
||||
}
|
||||
|
||||
public String asText() {
|
||||
return asText("Name", "Query", queriesByName);
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
|
||||
new InvalidProvider<InternalChangeQuery>(),
|
||||
new InvalidProvider<ChangeQueryRewriter>(),
|
||||
null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null));
|
||||
null, null, null, null, null, null, null, null, null));
|
||||
|
||||
private static final QueryRewriter.Definition<ChangeData, BasicChangeRewrites> mydef =
|
||||
new QueryRewriter.Definition<>(BasicChangeRewrites.class, BUILDER);
|
||||
|
@ -34,8 +34,11 @@ import com.google.gerrit.server.account.AccountResolver;
|
||||
import com.google.gerrit.server.account.CapabilityControl;
|
||||
import com.google.gerrit.server.account.GroupBackend;
|
||||
import com.google.gerrit.server.account.GroupBackends;
|
||||
import com.google.gerrit.server.account.VersionedAccountQueries;
|
||||
import com.google.gerrit.server.change.ChangeTriplet;
|
||||
import com.google.gerrit.server.config.AllProjectsName;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.config.AllUsersNameProvider;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gerrit.server.config.TrackingFooters;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
@ -57,9 +60,13 @@ import com.google.inject.Provider;
|
||||
import com.google.inject.ProvisionException;
|
||||
import com.google.inject.util.Providers;
|
||||
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.eclipse.jgit.lib.AbbreviatedObjectId;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@ -108,6 +115,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
public static final String FIELD_PATH = "path";
|
||||
public static final String FIELD_PROJECT = "project";
|
||||
public static final String FIELD_PROJECTS = "projects";
|
||||
public static final String FIELD_QUERY = "query";
|
||||
public static final String FIELD_REF = "ref";
|
||||
public static final String FIELD_REVIEWER = "reviewer";
|
||||
public static final String FIELD_REVIEWERIN = "reviewerin";
|
||||
@ -139,6 +147,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
final AccountResolver accountResolver;
|
||||
final GroupBackend groupBackend;
|
||||
final AllProjectsName allProjectsName;
|
||||
final AllUsersNameProvider allUsersName;
|
||||
final PatchListCache patchListCache;
|
||||
final GitRepositoryManager repoManager;
|
||||
final ProjectCache projectCache;
|
||||
@ -166,6 +175,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
AccountResolver accountResolver,
|
||||
GroupBackend groupBackend,
|
||||
AllProjectsName allProjectsName,
|
||||
AllUsersNameProvider allUsersName,
|
||||
PatchListCache patchListCache,
|
||||
GitRepositoryManager repoManager,
|
||||
ProjectCache projectCache,
|
||||
@ -178,9 +188,9 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
this(db, queryProvider, rewriter, userFactory, self,
|
||||
capabilityControlFactory, changeControlGenericFactory,
|
||||
changeDataFactory, fillArgs, plcUtil, accountResolver, groupBackend,
|
||||
allProjectsName, patchListCache, repoManager, projectCache,
|
||||
listChildProjects, indexes, submitStrategyFactory, conflictsCache,
|
||||
trackingFooters,
|
||||
allProjectsName, allUsersName, patchListCache, repoManager,
|
||||
projectCache, listChildProjects, indexes, submitStrategyFactory,
|
||||
conflictsCache, trackingFooters,
|
||||
cfg == null ? true : cfg.getBoolean("change", "allowDrafts", true));
|
||||
}
|
||||
|
||||
@ -198,6 +208,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
AccountResolver accountResolver,
|
||||
GroupBackend groupBackend,
|
||||
AllProjectsName allProjectsName,
|
||||
AllUsersNameProvider allUsersName,
|
||||
PatchListCache patchListCache,
|
||||
GitRepositoryManager repoManager,
|
||||
ProjectCache projectCache,
|
||||
@ -220,6 +231,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
this.accountResolver = accountResolver;
|
||||
this.groupBackend = groupBackend;
|
||||
this.allProjectsName = allProjectsName;
|
||||
this.allUsersName = allUsersName;
|
||||
this.patchListCache = patchListCache;
|
||||
this.repoManager = repoManager;
|
||||
this.projectCache = projectCache;
|
||||
@ -236,9 +248,9 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
Providers.of(otherUser),
|
||||
capabilityControlFactory, changeControlGenericFactory,
|
||||
changeDataFactory, fillArgs, plcUtil, accountResolver, groupBackend,
|
||||
allProjectsName, patchListCache, repoManager, projectCache,
|
||||
listChildProjects, indexes, submitStrategyFactory, conflictsCache,
|
||||
trackingFooters, allowsDrafts);
|
||||
allProjectsName, allUsersName, patchListCache, repoManager,
|
||||
projectCache, listChildProjects, indexes, submitStrategyFactory,
|
||||
conflictsCache, trackingFooters, allowsDrafts);
|
||||
}
|
||||
|
||||
Arguments asUser(Account.Id otherId) {
|
||||
@ -773,6 +785,25 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
return Predicate.or(owner(ownerIds), commentby(ownerIds));
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> query(String name) throws QueryParseException {
|
||||
AllUsersName allUsers = args.allUsersName.get();
|
||||
try (Repository git = args.repoManager.openRepository(allUsers)) {
|
||||
VersionedAccountQueries q = VersionedAccountQueries.forUser(self());
|
||||
q.load(git);
|
||||
String query = q.getQueryList().getQuery(name);
|
||||
if (query != null) {
|
||||
return parse(query);
|
||||
}
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
throw new QueryParseException("Unknown named query (no " +
|
||||
allUsers.get() +" repo): " + name, e);
|
||||
} catch (IOException | ConfigInvalidException e) {
|
||||
throw new QueryParseException("Error parsing named query: " + name, e);
|
||||
}
|
||||
throw new QueryParseException("Unknown named query: " + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
|
||||
if (query.startsWith("refs/")) {
|
||||
|
@ -0,0 +1,121 @@
|
||||
// Copyright (C) 2015 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.git;
|
||||
|
||||
import static org.easymock.EasyMock.createNiceMock;
|
||||
import static org.easymock.EasyMock.replay;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class QueryListTest extends TestCase {
|
||||
public static final String Q_P = "project:foo";
|
||||
public static final String Q_B = "branch:bar";
|
||||
public static final String Q_COMPLEX = "branch:bar AND peers:'is:open\t'";
|
||||
|
||||
public static final String N_FOO = "foo";
|
||||
public static final String N_BAR = "bar";
|
||||
|
||||
public static final String L_FOO = N_FOO + "\t" + Q_P + "\n";
|
||||
public static final String L_BAR = N_BAR + "\t" + Q_B + "\n";
|
||||
public static final String L_FOO_PROP = N_FOO + " \t" + Q_P + "\n";
|
||||
public static final String L_BAR_PROP = N_BAR + " \t" + Q_B + "\n";
|
||||
public static final String L_FOO_PAD_F = " " + N_FOO + "\t" + Q_P + "\n";
|
||||
public static final String L_FOO_PAD_E = N_FOO + " \t" + Q_P + "\n";
|
||||
public static final String L_BAR_PAD_F = N_BAR + "\t " + Q_B + "\n";
|
||||
public static final String L_BAR_PAD_E = N_BAR + "\t" + Q_B + " \n";
|
||||
public static final String L_COMPLEX = N_FOO + "\t" + Q_COMPLEX + "\t \n";
|
||||
public static final String L_BAD = N_FOO + "\n";
|
||||
|
||||
public static final String HEADER = "# Name\tQuery\n";
|
||||
public static final String C1 = "# A Simple Comment\n";
|
||||
public static final String C2 = "# Comment with a tab\t and multi # # #\n";
|
||||
|
||||
public static final String F_SIMPLE = L_FOO + L_BAR;
|
||||
public static final String F_PROPER = L_BAR_PROP + L_FOO_PROP; // alpha order
|
||||
public static final String F_PAD_F = L_FOO_PAD_F + L_BAR_PAD_F;
|
||||
public static final String F_PAD_E = L_FOO_PAD_E + L_BAR_PAD_E;
|
||||
|
||||
@Test
|
||||
public void testParseSimple() throws Exception {
|
||||
QueryList ql = QueryList.parse(F_SIMPLE, null);
|
||||
assertThat(ql.getQuery(N_FOO)).isEqualTo(Q_P);
|
||||
assertThat(ql.getQuery(N_BAR)).isEqualTo(Q_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseWHeader() throws Exception {
|
||||
QueryList ql = QueryList.parse(HEADER + F_SIMPLE, null);
|
||||
assertThat(ql.getQuery(N_FOO)).isEqualTo(Q_P);
|
||||
assertThat(ql.getQuery(N_BAR)).isEqualTo(Q_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseWComments() throws Exception {
|
||||
QueryList ql = QueryList.parse(C1 + F_SIMPLE + C2, null);
|
||||
assertThat(ql.getQuery(N_FOO)).isEqualTo(Q_P);
|
||||
assertThat(ql.getQuery(N_BAR)).isEqualTo(Q_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseFooComment() throws Exception {
|
||||
QueryList ql = QueryList.parse("#" + L_FOO + L_BAR, null);
|
||||
assertThat(ql.getQuery(N_FOO)).isNull();
|
||||
assertThat(ql.getQuery(N_BAR)).isEqualTo(Q_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePaddedFronts() throws Exception {
|
||||
QueryList ql = QueryList.parse(F_PAD_F, null);
|
||||
assertThat(ql.getQuery(N_FOO)).isEqualTo(Q_P);
|
||||
assertThat(ql.getQuery(N_BAR)).isEqualTo(Q_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePaddedEnds() throws Exception {
|
||||
QueryList ql = QueryList.parse(F_PAD_E, null);
|
||||
assertThat(ql.getQuery(N_FOO)).isEqualTo(Q_P);
|
||||
assertThat(ql.getQuery(N_BAR)).isEqualTo(Q_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseComplex() throws Exception {
|
||||
QueryList ql = QueryList.parse(L_COMPLEX, null);
|
||||
assertThat(ql.getQuery(N_FOO)).isEqualTo(Q_COMPLEX);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testParseBad() throws Exception {
|
||||
ValidationError.Sink sink = createNiceMock(ValidationError.Sink.class);
|
||||
replay(sink);
|
||||
QueryList.parse(L_BAD, sink);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsText() throws Exception {
|
||||
String expectedText = HEADER + "#\n" + F_PROPER;
|
||||
QueryList ql = QueryList.parse(F_SIMPLE, null);
|
||||
String asText = ql.asText();
|
||||
assertThat(asText).isEqualTo(expectedText);
|
||||
|
||||
ql = QueryList.parse(asText, null);
|
||||
asText = ql.asText();
|
||||
assertThat(asText).isEqualTo(expectedText);
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ public class FakeQueryBuilder extends ChangeQueryBuilder {
|
||||
FakeQueryBuilder.class),
|
||||
new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null,
|
||||
indexes, null, null, null, null));
|
||||
null, indexes, null, null, null, null));
|
||||
}
|
||||
|
||||
@Operator
|
||||
|
Loading…
x
Reference in New Issue
Block a user