Merge "Support IsOperands in change queries from plugins"

This commit is contained in:
Martin Fick
2020-08-28 19:38:58 +00:00
committed by Gerrit Code Review
5 changed files with 123 additions and 2 deletions

View File

@@ -742,7 +742,8 @@ Plugins can define new search operands to extend change searching.
Plugin methods implementing search operands (returning a Plugin methods implementing search operands (returning a
`Predicate<ChangeData>`), must be defined on a class implementing `Predicate<ChangeData>`), must be defined on a class implementing
one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces
(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory). The specific (.e.g., ChangeQueryBuilder.ChangeHasOperandFactory or
ChangeQueryBuilder.ChangeIsOperandFactory). The specific
`ChangeOperandFactory` class must also be bound to the `DynamicSet` from `ChangeOperandFactory` class must also be bound to the `DynamicSet` from
a module's `configure()` method in the plugin. a module's `configure()` method in the plugin.

View File

@@ -433,6 +433,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicMap.mapOf(binder(), DynamicOptions.DynamicBean.class); DynamicMap.mapOf(binder(), DynamicOptions.DynamicBean.class);
DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class); DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class);
DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeHasOperandFactory.class); DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeHasOperandFactory.class);
DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeIsOperandFactory.class);
DynamicSet.setOf(binder(), ChangeAttributeFactory.class); DynamicSet.setOf(binder(), ChangeAttributeFactory.class);
install(new GitwebConfig.LegacyModule(cfg)); install(new GitwebConfig.LegacyModule(cfg));

View File

@@ -117,12 +117,14 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
* <p>bind(ChangeHasOperandFactory.class) .annotatedWith(Exports.named("your has operand")) * <p>bind(ChangeHasOperandFactory.class) .annotatedWith(Exports.named("your has operand"))
* .to(YourClass.class); * .to(YourClass.class);
*/ */
private interface ChangeOperandFactory { public interface ChangeOperandFactory {
Predicate<ChangeData> create(ChangeQueryBuilder builder) throws QueryParseException; Predicate<ChangeData> create(ChangeQueryBuilder builder) throws QueryParseException;
} }
public interface ChangeHasOperandFactory extends ChangeOperandFactory {} public interface ChangeHasOperandFactory extends ChangeOperandFactory {}
public interface ChangeIsOperandFactory extends ChangeOperandFactory {}
private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$"); private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$");
private static final Pattern PAT_CHANGE_ID = Pattern.compile(CHANGE_ID_PATTERN); private static final Pattern PAT_CHANGE_ID = Pattern.compile(CHANGE_ID_PATTERN);
private static final Pattern DEF_CHANGE = private static final Pattern DEF_CHANGE =
@@ -218,6 +220,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
final CommentsUtil commentsUtil; final CommentsUtil commentsUtil;
final ConflictsCache conflictsCache; final ConflictsCache conflictsCache;
final DynamicMap<ChangeHasOperandFactory> hasOperands; final DynamicMap<ChangeHasOperandFactory> hasOperands;
final DynamicMap<ChangeIsOperandFactory> isOperands;
final DynamicMap<ChangeOperatorFactory> opFactories; final DynamicMap<ChangeOperatorFactory> opFactories;
final GitRepositoryManager repoManager; final GitRepositoryManager repoManager;
final GroupBackend groupBackend; final GroupBackend groupBackend;
@@ -244,6 +247,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
ChangeIndexRewriter rewriter, ChangeIndexRewriter rewriter,
DynamicMap<ChangeOperatorFactory> opFactories, DynamicMap<ChangeOperatorFactory> opFactories,
DynamicMap<ChangeHasOperandFactory> hasOperands, DynamicMap<ChangeHasOperandFactory> hasOperands,
DynamicMap<ChangeIsOperandFactory> isOperands,
IdentifiedUser.GenericFactory userFactory, IdentifiedUser.GenericFactory userFactory,
Provider<CurrentUser> self, Provider<CurrentUser> self,
PermissionBackend permissionBackend, PermissionBackend permissionBackend,
@@ -273,6 +277,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
rewriter, rewriter,
opFactories, opFactories,
hasOperands, hasOperands,
isOperands,
userFactory, userFactory,
self, self,
permissionBackend, permissionBackend,
@@ -304,6 +309,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
ChangeIndexRewriter rewriter, ChangeIndexRewriter rewriter,
DynamicMap<ChangeOperatorFactory> opFactories, DynamicMap<ChangeOperatorFactory> opFactories,
DynamicMap<ChangeHasOperandFactory> hasOperands, DynamicMap<ChangeHasOperandFactory> hasOperands,
DynamicMap<ChangeIsOperandFactory> isOperands,
IdentifiedUser.GenericFactory userFactory, IdentifiedUser.GenericFactory userFactory,
Provider<CurrentUser> self, Provider<CurrentUser> self,
PermissionBackend permissionBackend, PermissionBackend permissionBackend,
@@ -351,6 +357,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
this.starredChangesUtil = starredChangesUtil; this.starredChangesUtil = starredChangesUtil;
this.accountCache = accountCache; this.accountCache = accountCache;
this.hasOperands = hasOperands; this.hasOperands = hasOperands;
this.isOperands = isOperands;
this.groupMembers = groupMembers; this.groupMembers = groupMembers;
this.changeIsVisbleToPredicateFactory = changeIsVisbleToPredicateFactory; this.changeIsVisbleToPredicateFactory = changeIsVisbleToPredicateFactory;
this.operatorAliasConfig = operatorAliasConfig; this.operatorAliasConfig = operatorAliasConfig;
@@ -364,6 +371,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
rewriter, rewriter,
opFactories, opFactories,
hasOperands, hasOperands,
isOperands,
userFactory, userFactory,
Providers.of(otherUser), Providers.of(otherUser),
permissionBackend, permissionBackend,
@@ -643,6 +651,14 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
throw new QueryParseException("'is:wip' operator is not supported by change index version"); throw new QueryParseException("'is:wip' operator is not supported by change index version");
} }
// for plugins the value will be operandName_pluginName
List<String> names = Lists.newArrayList(Splitter.on('_').split(value));
if (names.size() == 2) {
ChangeIsOperandFactory op = args.isOperands.get(names.get(1), names.get(0));
if (op != null) {
return op.create(this);
}
}
return status(value); return status(value);
} }

View File

@@ -0,0 +1,102 @@
// Copyright (C) 2020 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.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.index.query.Matchable;
import com.google.gerrit.index.query.OperatorPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.restapi.change.QueryChanges;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.List;
import org.junit.Test;
public class PluginOperatorsIT extends AbstractDaemonTest {
@Inject private Provider<QueryChanges> queryChangesProvider;
@Test
public void getChangeWithIsOperator() throws Exception {
QueryChanges queryChanges = queryChangesProvider.get();
queryChanges.addQuery("is:changeNumberEven_myplugin");
String oddChangeId = createChange().getChangeId();
String evenChangeId = createChange().getChangeId();
assertThat(getChanges(queryChanges)).hasSize(0);
try (AutoCloseable ignored = installPlugin("myplugin", IsOperatorModule.class)) {
List<ChangeInfo> changes = getChanges(queryChanges);
assertThat(changes).hasSize(1);
String outputChangeId = ((ChangeInfo) changes.get(0)).changeId;
assertThat(outputChangeId).isEqualTo(evenChangeId);
assertThat(outputChangeId).isNotEqualTo(oddChangeId);
}
assertThat(getChanges(queryChanges)).hasSize(0);
}
protected static class IsOperatorModule extends AbstractModule {
@Override
public void configure() {
bind(ChangeQueryBuilder.ChangeIsOperandFactory.class)
.annotatedWith(Exports.named("changeNumberEven"))
.to(SampleIsOperand.class);
}
}
private static class SampleIsOperand implements ChangeQueryBuilder.ChangeIsOperandFactory {
@Override
public Predicate<ChangeData> create(ChangeQueryBuilder builder) throws QueryParseException {
return new IsSamplePredicate();
}
}
private static class IsSamplePredicate extends OperatorPredicate<ChangeData>
implements Matchable<ChangeData> {
public IsSamplePredicate() {
super("is", "changeNumberEven");
}
@Override
public boolean match(ChangeData changeData) {
int id = changeData.getId().get();
return id % 2 == 0;
}
@Override
public int getCost() {
return 0;
}
}
private List<ChangeInfo> getChanges(QueryChanges queryChanges)
throws AuthException, PermissionBackendException, BadRequestException {
return (List<ChangeInfo>) queryChanges.apply(TopLevelResource.INSTANCE).value();
}
}

View File

@@ -44,6 +44,7 @@ public class FakeQueryBuilder extends ChangeQueryBuilder {
null, null,
null, null,
null, null,
null,
indexes, indexes,
null, null,
null, null,