Add STATE field to ProjectIndex

This commit adds the ProjectState to the project index and makes it
queryable. V2 was added in this series, so adding a field does not
require creating V3.

Our post processors filter out hidden projects, so it doesn't make much
sense for users to query for them. As a future optimisation we can make
the default query exclude hidden projects by adding 'not state:hidden'
so that we have to do less post-filtering.

Change-Id: I98434e4b4ea232e257cca4b551fcaba21b3db656
This commit is contained in:
Patrick Hiesel 2018-07-06 10:57:52 +02:00
parent 814d296805
commit 36de336dc1
7 changed files with 81 additions and 3 deletions

View File

@ -24,6 +24,11 @@ description:'DESCRIPTION'::
Matches projects whose description contains 'DESCRIPTION', using a
full-text search.
[[state]]
state:'STATE'::
+
Matches project's state. Can be either 'active' or 'read-only'.
== Magical Operators
[[is-visible]]

View File

@ -45,6 +45,9 @@ public class ProjectField {
public static final FieldDef<ProjectData, Iterable<String>> NAME_PART =
prefix("name_part").buildRepeatable(p -> SchemaUtil.getNameParts(p.getProject().getName()));
public static final FieldDef<ProjectData, String> STATE =
exact("state").stored().build(p -> p.getProject().getState().name());
public static final FieldDef<ProjectData, Iterable<String>> ANCESTOR_NAME =
exact("ancestor_name").buildRepeatable(p -> p.getParentNames());

View File

@ -30,7 +30,7 @@ public class ProjectSchemaDefinitions extends SchemaDefinitions<ProjectData> {
ProjectField.NAME_PART,
ProjectField.ANCESTOR_NAME);
static final Schema<ProjectData> V2 = schema(V1, ProjectField.REF_STATE);
static final Schema<ProjectData> V2 = schema(V1, ProjectField.STATE, ProjectField.REF_STATE);
public static final ProjectSchemaDefinitions INSTANCE = new ProjectSchemaDefinitions();

View File

@ -14,6 +14,7 @@
package com.google.gerrit.server.query.project;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectField;
import com.google.gerrit.index.project.ProjectPredicate;
@ -34,5 +35,9 @@ public class ProjectPredicates {
return new ProjectPredicate(ProjectField.DESCRIPTION, description);
}
public static Predicate<ProjectData> state(ProjectState state) {
return new ProjectPredicate(ProjectField.STATE, state.name());
}
private ProjectPredicates() {}
}

View File

@ -17,6 +17,7 @@ package com.google.gerrit.server.query.project;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.query.LimitPredicate;
import com.google.gerrit.index.query.Predicate;
@ -60,6 +61,23 @@ public class ProjectQueryBuilder extends QueryBuilder<ProjectData> {
return ProjectPredicates.description(description);
}
@Operator
public Predicate<ProjectData> state(String state) throws QueryParseException {
if (Strings.isNullOrEmpty(state)) {
throw error("state operator requires a value");
}
ProjectState parsedState;
try {
parsedState = ProjectState.valueOf(state.replace('-', '_').toUpperCase());
} catch (IllegalArgumentException e) {
throw error("state operator must be either 'active' or 'read-only'");
}
if (parsedState == ProjectState.HIDDEN) {
throw error("state operator must be either 'active' or 'read-only'");
}
return ProjectPredicates.state(parsedState);
}
@Override
protected Predicate<ProjectData> defaultField(String query) throws QueryParseException {
// Adapt the capacity of this list when adding more default predicates.

View File

@ -15,15 +15,21 @@
package com.google.gerrit.server.query.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static java.util.stream.Collectors.toList;
import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.api.projects.Projects.QueryRequest;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
@ -38,7 +44,6 @@ import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
@ -83,7 +88,7 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
@Inject protected OneOffRequestContext oneOffRequestContext;
@Inject protected InternalAccountQuery internalAccountQuery;
@Inject protected ProjectIndexCollection indexes;
@Inject protected AllProjectsName allProjects;
@ -210,6 +215,30 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
assertQuery("description:\"\"");
}
@Test
public void byState() throws Exception {
assume().that(getSchemaVersion() >= 2).isTrue();
ProjectInfo project1 = createProjectWithState(name("project1"), ProjectState.ACTIVE);
ProjectInfo project2 = createProjectWithState(name("project2"), ProjectState.READ_ONLY);
assertQuery("state:active", project1);
assertQuery("state:read-only", project2);
}
@Test
public void byState_emptyQuery() throws Exception {
exception.expect(BadRequestException.class);
exception.expectMessage("state operator requires a value");
assertQuery("state:\"\"");
}
@Test
public void byState_badQuery() throws Exception {
exception.expect(BadRequestException.class);
exception.expectMessage("state operator must be either 'active' or 'read-only'");
assertQuery("state:bla");
}
@Test
public void byDefaultField() throws Exception {
ProjectInfo project1 = createProject(name("foo-project"));
@ -291,6 +320,14 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
return gApi.projects().create(in).get();
}
protected ProjectInfo createProjectWithState(String name, ProjectState state) throws Exception {
ProjectInfo info = createProject(name);
ConfigInput config = new ConfigInput();
config.state = state;
gApi.projects().name(info.name).config(config);
return info;
}
protected ProjectInfo getProject(Project.NameKey nameKey) throws Exception {
return gApi.projects().name(nameKey.get()).get();
}
@ -354,6 +391,14 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
return b.toString();
}
protected int getSchemaVersion() {
return getSchema().getVersion();
}
protected Schema<ProjectData> getSchema() {
return indexes.getSearchIndex().getSchema();
}
protected static Iterable<String> names(ProjectInfo... projects) {
return names(Arrays.asList(projects));
}

View File

@ -9,6 +9,8 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index/project",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",