Add attention set to index
Change-Id: I22d2aa7b73cbbdf0d24cc4e49a0263705ea0229d
This commit is contained in:
@@ -73,6 +73,11 @@ assignee:'USER'::
|
|||||||
+
|
+
|
||||||
Changes assigned to the given user.
|
Changes assigned to the given user.
|
||||||
|
|
||||||
|
[[attention]]
|
||||||
|
attention:'USER'::
|
||||||
|
+
|
||||||
|
Changes whose attention set includes the given user.
|
||||||
|
|
||||||
[[before_until]]
|
[[before_until]]
|
||||||
before:'TIME'/until:'TIME'::
|
before:'TIME'/until:'TIME'::
|
||||||
+
|
+
|
||||||
|
@@ -405,6 +405,15 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
|
|||||||
// Unresolved-comment-count.
|
// Unresolved-comment-count.
|
||||||
decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
|
decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
|
||||||
|
|
||||||
|
// Attention set.
|
||||||
|
if (fields.contains(ChangeField.ATTENTION_SET_FULL.getName())) {
|
||||||
|
ChangeField.parseAttentionSet(
|
||||||
|
FluentIterable.from(source.getAsJsonArray(ChangeField.ATTENTION_SET_FULL.getName()))
|
||||||
|
.transform(JsonElement::getAsString)
|
||||||
|
.toSet(),
|
||||||
|
cd);
|
||||||
|
}
|
||||||
|
|
||||||
return cd;
|
return cd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -91,7 +91,9 @@ public final class FieldDef<I, T> {
|
|||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final FieldType<?> type;
|
private final FieldType<?> type;
|
||||||
|
/** Allow reading the actual data from the index. */
|
||||||
private final boolean stored;
|
private final boolean stored;
|
||||||
|
|
||||||
private final boolean repeatable;
|
private final boolean repeatable;
|
||||||
private final Getter<I, T> getter;
|
private final Getter<I, T> getter;
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
package com.google.gerrit.lucene;
|
package com.google.gerrit.lucene;
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static com.google.gerrit.lucene.AbstractLuceneIndex.sortFieldName;
|
import static com.google.gerrit.lucene.AbstractLuceneIndex.sortFieldName;
|
||||||
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
|
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
|
||||||
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
|
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
|
||||||
@@ -138,6 +139,7 @@ public class LuceneChangeIndex implements ChangeIndex {
|
|||||||
private static final String TOTAL_COMMENT_COUNT_FIELD = ChangeField.TOTAL_COMMENT_COUNT.getName();
|
private static final String TOTAL_COMMENT_COUNT_FIELD = ChangeField.TOTAL_COMMENT_COUNT.getName();
|
||||||
private static final String UNRESOLVED_COMMENT_COUNT_FIELD =
|
private static final String UNRESOLVED_COMMENT_COUNT_FIELD =
|
||||||
ChangeField.UNRESOLVED_COMMENT_COUNT.getName();
|
ChangeField.UNRESOLVED_COMMENT_COUNT.getName();
|
||||||
|
private static final String ATTENTION_SET_FULL_FIELD = ChangeField.ATTENTION_SET_FULL.getName();
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
static interface IdTerm {
|
static interface IdTerm {
|
||||||
@@ -548,6 +550,9 @@ public class LuceneChangeIndex implements ChangeIndex {
|
|||||||
if (fields.contains(PENDING_REVIEWER_BY_EMAIL_FIELD)) {
|
if (fields.contains(PENDING_REVIEWER_BY_EMAIL_FIELD)) {
|
||||||
decodePendingReviewersByEmail(doc, cd);
|
decodePendingReviewersByEmail(doc, cd);
|
||||||
}
|
}
|
||||||
|
if (fields.contains(ATTENTION_SET_FULL_FIELD)) {
|
||||||
|
decodeAttentionSet(doc, cd);
|
||||||
|
}
|
||||||
decodeSubmitRecords(
|
decodeSubmitRecords(
|
||||||
doc, SUBMIT_RECORD_STRICT_FIELD, ChangeField.SUBMIT_RULE_OPTIONS_STRICT, cd);
|
doc, SUBMIT_RECORD_STRICT_FIELD, ChangeField.SUBMIT_RULE_OPTIONS_STRICT, cd);
|
||||||
decodeSubmitRecords(
|
decodeSubmitRecords(
|
||||||
@@ -672,6 +677,14 @@ public class LuceneChangeIndex implements ChangeIndex {
|
|||||||
.transform(IndexableField::stringValue)));
|
.transform(IndexableField::stringValue)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void decodeAttentionSet(ListMultimap<String, IndexableField> doc, ChangeData cd) {
|
||||||
|
ChangeField.parseAttentionSet(
|
||||||
|
doc.get(ATTENTION_SET_FULL_FIELD).stream()
|
||||||
|
.map(field -> field.binaryValue().utf8ToString())
|
||||||
|
.collect(toImmutableSet()),
|
||||||
|
cd);
|
||||||
|
}
|
||||||
|
|
||||||
private void decodeSubmitRecords(
|
private void decodeSubmitRecords(
|
||||||
ListMultimap<String, IndexableField> doc,
|
ListMultimap<String, IndexableField> doc,
|
||||||
String field,
|
String field,
|
||||||
|
@@ -33,6 +33,7 @@ import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_DIFFSTA
|
|||||||
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
|
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
|
||||||
import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS;
|
import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS;
|
||||||
import static com.google.gerrit.server.ChangeMessagesUtil.createChangeMessageInfo;
|
import static com.google.gerrit.server.ChangeMessagesUtil.createChangeMessageInfo;
|
||||||
|
import static com.google.gerrit.server.util.AttentionSetUtil.additionsOnly;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
@@ -50,7 +51,6 @@ import com.google.gerrit.common.data.SubmitRecord.Status;
|
|||||||
import com.google.gerrit.common.data.SubmitRequirement;
|
import com.google.gerrit.common.data.SubmitRequirement;
|
||||||
import com.google.gerrit.common.data.SubmitTypeRecord;
|
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||||
import com.google.gerrit.entities.Account;
|
import com.google.gerrit.entities.Account;
|
||||||
import com.google.gerrit.entities.AttentionSetUpdate;
|
|
||||||
import com.google.gerrit.entities.Change;
|
import com.google.gerrit.entities.Change;
|
||||||
import com.google.gerrit.entities.ChangeMessage;
|
import com.google.gerrit.entities.ChangeMessage;
|
||||||
import com.google.gerrit.entities.PatchSet;
|
import com.google.gerrit.entities.PatchSet;
|
||||||
@@ -510,9 +510,8 @@ public class ChangeJson {
|
|||||||
out.topic = in.getTopic();
|
out.topic = in.getTopic();
|
||||||
if (!cd.attentionSet().isEmpty()) {
|
if (!cd.attentionSet().isEmpty()) {
|
||||||
out.attentionSet =
|
out.attentionSet =
|
||||||
cd.attentionSet().stream()
|
// This filtering should match GetAttentionSet.
|
||||||
// This filtering should match GetAttentionSet.
|
additionsOnly(cd.attentionSet()).stream()
|
||||||
.filter(a -> a.operation() == AttentionSetUpdate.Operation.ADD)
|
|
||||||
.collect(
|
.collect(
|
||||||
toImmutableMap(
|
toImmutableMap(
|
||||||
a -> a.account().get(),
|
a -> a.account().get(),
|
||||||
|
@@ -24,6 +24,7 @@ import static com.google.gerrit.index.FieldDef.integer;
|
|||||||
import static com.google.gerrit.index.FieldDef.prefix;
|
import static com.google.gerrit.index.FieldDef.prefix;
|
||||||
import static com.google.gerrit.index.FieldDef.storedOnly;
|
import static com.google.gerrit.index.FieldDef.storedOnly;
|
||||||
import static com.google.gerrit.index.FieldDef.timestamp;
|
import static com.google.gerrit.index.FieldDef.timestamp;
|
||||||
|
import static com.google.gerrit.server.util.AttentionSetUtil.additionsOnly;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
@@ -45,6 +46,8 @@ import com.google.common.primitives.Longs;
|
|||||||
import com.google.gerrit.common.data.SubmitRecord;
|
import com.google.gerrit.common.data.SubmitRecord;
|
||||||
import com.google.gerrit.common.data.SubmitRequirement;
|
import com.google.gerrit.common.data.SubmitRequirement;
|
||||||
import com.google.gerrit.entities.Account;
|
import com.google.gerrit.entities.Account;
|
||||||
|
import com.google.gerrit.entities.AttentionSetUpdate;
|
||||||
|
import com.google.gerrit.entities.AttentionSetUpdate.Operation;
|
||||||
import com.google.gerrit.entities.Change;
|
import com.google.gerrit.entities.Change;
|
||||||
import com.google.gerrit.entities.ChangeMessage;
|
import com.google.gerrit.entities.ChangeMessage;
|
||||||
import com.google.gerrit.entities.PatchSetApproval;
|
import com.google.gerrit.entities.PatchSetApproval;
|
||||||
@@ -74,6 +77,7 @@ import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
|||||||
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
|
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -288,6 +292,45 @@ public class ChangeField {
|
|||||||
? cd.change().getCherryPickOf().get()
|
? cd.change().getCherryPickOf().get()
|
||||||
: null);
|
: null);
|
||||||
|
|
||||||
|
/** This class decouples the internal and API types from storage. */
|
||||||
|
private static class StoredAttentionSetEntry {
|
||||||
|
final long timestampMillis;
|
||||||
|
final int userId;
|
||||||
|
final String reason;
|
||||||
|
final Operation operation;
|
||||||
|
|
||||||
|
StoredAttentionSetEntry(AttentionSetUpdate attentionSetUpdate) {
|
||||||
|
timestampMillis = attentionSetUpdate.timestamp().toEpochMilli();
|
||||||
|
userId = attentionSetUpdate.account().get();
|
||||||
|
reason = attentionSetUpdate.reason();
|
||||||
|
operation = attentionSetUpdate.operation();
|
||||||
|
}
|
||||||
|
|
||||||
|
AttentionSetUpdate toAttentionSetUpdate() {
|
||||||
|
return AttentionSetUpdate.createFromRead(
|
||||||
|
Instant.ofEpochMilli(timestampMillis), Account.id(userId), operation, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users included in the attention set of the change. This omits timestamp, reason and possible
|
||||||
|
* future fields.
|
||||||
|
*
|
||||||
|
* @see #ATTENTION_SET_FULL
|
||||||
|
*/
|
||||||
|
public static final FieldDef<ChangeData, Iterable<Integer>> ATTENTION_SET_USERS =
|
||||||
|
integer(ChangeQueryBuilder.FIELD_ATTENTION_SET_USERS)
|
||||||
|
.buildRepeatable(ChangeField::getAttentionSetUserIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The full attention set data including timestamp, reason and possible future fields.
|
||||||
|
*
|
||||||
|
* @see #ATTENTION_SET_USERS
|
||||||
|
*/
|
||||||
|
public static final FieldDef<ChangeData, Iterable<byte[]>> ATTENTION_SET_FULL =
|
||||||
|
storedOnly(ChangeQueryBuilder.FIELD_ATTENTION_SET_FULL)
|
||||||
|
.buildRepeatable(ChangeField::storedAttentionSet);
|
||||||
|
|
||||||
/** The user assigned to the change. */
|
/** The user assigned to the change. */
|
||||||
public static final FieldDef<ChangeData, Integer> ASSIGNEE =
|
public static final FieldDef<ChangeData, Integer> ASSIGNEE =
|
||||||
integer(ChangeQueryBuilder.FIELD_ASSIGNEE)
|
integer(ChangeQueryBuilder.FIELD_ASSIGNEE)
|
||||||
@@ -463,6 +506,33 @@ public class ChangeField {
|
|||||||
return ReviewerByEmailSet.fromTable(b.build());
|
return ReviewerByEmailSet.fromTable(b.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ImmutableSet<Integer> getAttentionSetUserIds(ChangeData changeData) {
|
||||||
|
return additionsOnly(changeData.attentionSet()).stream()
|
||||||
|
.map(update -> update.account().get())
|
||||||
|
.collect(toImmutableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableSet<byte[]> storedAttentionSet(ChangeData changeData) {
|
||||||
|
return changeData.attentionSet().stream()
|
||||||
|
.map(StoredAttentionSetEntry::new)
|
||||||
|
.map(storedAttentionSetEntry -> GSON.toJson(storedAttentionSetEntry).getBytes(UTF_8))
|
||||||
|
.collect(toImmutableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes the specified attention set entries from JSON and stores them in the specified
|
||||||
|
* change.
|
||||||
|
*/
|
||||||
|
public static void parseAttentionSet(
|
||||||
|
Collection<String> storedAttentionSetEntriesJson, ChangeData changeData) {
|
||||||
|
ImmutableSet<AttentionSetUpdate> attentionSet =
|
||||||
|
storedAttentionSetEntriesJson.stream()
|
||||||
|
.map(
|
||||||
|
entry -> GSON.fromJson(entry, StoredAttentionSetEntry.class).toAttentionSetUpdate())
|
||||||
|
.collect(toImmutableSet());
|
||||||
|
changeData.setAttentionSet(attentionSet);
|
||||||
|
}
|
||||||
|
|
||||||
/** Commit ID of any patch set on the change, using prefix match. */
|
/** Commit ID of any patch set on the change, using prefix match. */
|
||||||
public static final FieldDef<ChangeData, Iterable<String>> COMMIT =
|
public static final FieldDef<ChangeData, Iterable<String>> COMMIT =
|
||||||
prefix(ChangeQueryBuilder.FIELD_COMMIT).buildRepeatable(ChangeField::getRevisions);
|
prefix(ChangeQueryBuilder.FIELD_COMMIT).buildRepeatable(ChangeField::getRevisions);
|
||||||
|
@@ -85,12 +85,17 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
|||||||
ChangeField.UPDATED,
|
ChangeField.UPDATED,
|
||||||
ChangeField.WIP);
|
ChangeField.WIP);
|
||||||
|
|
||||||
// The computation of the 'extension' field is changed, hence reindexing is required.
|
/**
|
||||||
|
* The computation of the {@link ChangeField#EXTENSION} field is changed, hence reindexing is
|
||||||
|
* required.
|
||||||
|
*/
|
||||||
@Deprecated static final Schema<ChangeData> V56 = schema(V55);
|
@Deprecated static final Schema<ChangeData> V56 = schema(V55);
|
||||||
|
|
||||||
// New numeric types: use dimensional points using the k-d tree geo-spatial data structure
|
/**
|
||||||
// to offer fast single- and multi-dimensional numeric range. As the consequense, integer
|
* New numeric types: use dimensional points using the k-d tree geo-spatial data structure to
|
||||||
// document id type is replaced with string document id type.
|
* offer fast single- and multi-dimensional numeric range. As the consequense, {@link
|
||||||
|
* ChangeField#LEGACY_ID} is replaced with {@link ChangeField#LEGACY_ID_STR}.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
static final Schema<ChangeData> V57 =
|
static final Schema<ChangeData> V57 =
|
||||||
new Schema.Builder<ChangeData>()
|
new Schema.Builder<ChangeData>()
|
||||||
@@ -100,7 +105,11 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
|||||||
.legacyNumericFields(false)
|
.legacyNumericFields(false)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Add new field CHERRY_PICK_OF
|
/**
|
||||||
|
* Added new fields {@link ChangeField#CHERRY_PICK_OF_CHANGE} and {@link
|
||||||
|
* ChangeField#CHERRY_PICK_OF_PATCHSET}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
static final Schema<ChangeData> V58 =
|
static final Schema<ChangeData> V58 =
|
||||||
new Schema.Builder<ChangeData>()
|
new Schema.Builder<ChangeData>()
|
||||||
.add(V57)
|
.add(V57)
|
||||||
@@ -108,6 +117,17 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
|||||||
.add(ChangeField.CHERRY_PICK_OF_PATCHSET)
|
.add(ChangeField.CHERRY_PICK_OF_PATCHSET)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Added new fields {@link ChangeField#ATTENTION_SET_USERS} and {@link
|
||||||
|
* ChangeField#ATTENTION_SET_FULL}.
|
||||||
|
*/
|
||||||
|
static final Schema<ChangeData> V59 =
|
||||||
|
new Schema.Builder<ChangeData>()
|
||||||
|
.add(V58)
|
||||||
|
.add(ChangeField.ATTENTION_SET_USERS)
|
||||||
|
.add(ChangeField.ATTENTION_SET_FULL)
|
||||||
|
.build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the change index to be used when contacting index backends or loading configurations.
|
* Name of the change index to be used when contacting index backends or loading configurations.
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,41 @@
|
|||||||
|
// 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.server.query.change;
|
||||||
|
|
||||||
|
import static com.google.gerrit.server.util.AttentionSetUtil.additionsOnly;
|
||||||
|
|
||||||
|
import com.google.gerrit.entities.Account;
|
||||||
|
import com.google.gerrit.server.index.change.ChangeField;
|
||||||
|
|
||||||
|
/** Simple predicate for searching by attention set. */
|
||||||
|
public class AttentionSetPredicate extends ChangeIndexPredicate {
|
||||||
|
protected final Account.Id id;
|
||||||
|
|
||||||
|
AttentionSetPredicate(Account.Id id) {
|
||||||
|
super(ChangeField.ATTENTION_SET_USERS, id.toString());
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean match(ChangeData changeData) {
|
||||||
|
return additionsOnly(changeData.attentionSet()).stream()
|
||||||
|
.anyMatch(update -> update.account().equals(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCost() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
@@ -611,6 +611,21 @@ public class ChangeData {
|
|||||||
return attentionSet;
|
return attentionSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the specified attention set. If two or more entries refer to the same user, throws an
|
||||||
|
* {@link IllegalStateException}.
|
||||||
|
*/
|
||||||
|
public void setAttentionSet(ImmutableSet<AttentionSetUpdate> attentionSet) {
|
||||||
|
if (attentionSet.stream().map(AttentionSetUpdate::account).distinct().count()
|
||||||
|
!= attentionSet.size()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"Stored attention set for change %d contains duplicate update",
|
||||||
|
change.getId().get()));
|
||||||
|
}
|
||||||
|
this.attentionSet = attentionSet;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return patches for the change, in patch set ID order. */
|
/** @return patches for the change, in patch set ID order. */
|
||||||
public Collection<PatchSet> patchSets() {
|
public Collection<PatchSet> patchSets() {
|
||||||
if (patchSets == null) {
|
if (patchSets == null) {
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.query.change;
|
package com.google.gerrit.server.query.change;
|
||||||
|
|
||||||
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static com.google.gerrit.entities.Change.CHANGE_ID_PATTERN;
|
import static com.google.gerrit.entities.Change.CHANGE_ID_PATTERN;
|
||||||
import static com.google.gerrit.server.account.AccountResolver.isSelf;
|
import static com.google.gerrit.server.account.AccountResolver.isSelf;
|
||||||
import static com.google.gerrit.server.query.change.ChangeData.asChanges;
|
import static com.google.gerrit.server.query.change.ChangeData.asChanges;
|
||||||
@@ -136,6 +137,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
|
|||||||
|
|
||||||
public static final String FIELD_ADDED = "added";
|
public static final String FIELD_ADDED = "added";
|
||||||
public static final String FIELD_AGE = "age";
|
public static final String FIELD_AGE = "age";
|
||||||
|
public static final String FIELD_ATTENTION_SET_USERS = "attentionusers";
|
||||||
|
public static final String FIELD_ATTENTION_SET_FULL = "attentionfull";
|
||||||
public static final String FIELD_ASSIGNEE = "assignee";
|
public static final String FIELD_ASSIGNEE = "assignee";
|
||||||
public static final String FIELD_AUTHOR = "author";
|
public static final String FIELD_AUTHOR = "author";
|
||||||
public static final String FIELD_EXACTAUTHOR = "exactauthor";
|
public static final String FIELD_EXACTAUTHOR = "exactauthor";
|
||||||
@@ -1056,6 +1059,20 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
|
|||||||
return owner(accounts);
|
return owner(accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operator
|
||||||
|
public Predicate<ChangeData> attention(String who)
|
||||||
|
throws QueryParseException, IOException, ConfigInvalidException {
|
||||||
|
if (!args.index.getSchema().hasField(ChangeField.ATTENTION_SET_USERS)) {
|
||||||
|
throw new QueryParseException(
|
||||||
|
"'attention' operator is not supported by change index version");
|
||||||
|
}
|
||||||
|
return attention(parseAccount(who, (AccountState s) -> true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<ChangeData> attention(Set<Account.Id> who) {
|
||||||
|
return Predicate.or(who.stream().map(AttentionSetPredicate::new).collect(toImmutableSet()));
|
||||||
|
}
|
||||||
|
|
||||||
@Operator
|
@Operator
|
||||||
public Predicate<ChangeData> assignee(String who)
|
public Predicate<ChangeData> assignee(String who)
|
||||||
throws QueryParseException, IOException, ConfigInvalidException {
|
throws QueryParseException, IOException, ConfigInvalidException {
|
||||||
|
@@ -15,9 +15,9 @@
|
|||||||
package com.google.gerrit.server.restapi.change;
|
package com.google.gerrit.server.restapi.change;
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
|
import static com.google.gerrit.server.util.AttentionSetUtil.additionsOnly;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.gerrit.entities.AttentionSetUpdate.Operation;
|
|
||||||
import com.google.gerrit.extensions.common.AttentionSetEntry;
|
import com.google.gerrit.extensions.common.AttentionSetEntry;
|
||||||
import com.google.gerrit.extensions.restapi.Response;
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
import com.google.gerrit.extensions.restapi.RestReadView;
|
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||||
@@ -45,9 +45,8 @@ public class GetAttentionSet implements RestReadView<ChangeResource> {
|
|||||||
throws PermissionBackendException {
|
throws PermissionBackendException {
|
||||||
AccountLoader accountLoader = accountLoaderFactory.create(true);
|
AccountLoader accountLoader = accountLoaderFactory.create(true);
|
||||||
ImmutableSet<AttentionSetEntry> response =
|
ImmutableSet<AttentionSetEntry> response =
|
||||||
changeResource.getNotes().getAttentionSet().stream()
|
// This filtering should match ChangeJson.
|
||||||
// This filtering should match ChangeJson.
|
additionsOnly(changeResource.getNotes().getAttentionSet()).stream()
|
||||||
.filter(a -> a.operation() == Operation.ADD)
|
|
||||||
.map(
|
.map(
|
||||||
a ->
|
a ->
|
||||||
new AttentionSetEntry(
|
new AttentionSetEntry(
|
||||||
|
19
java/com/google/gerrit/server/util/AttentionSetUtil.java
Normal file
19
java/com/google/gerrit/server/util/AttentionSetUtil.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.google.gerrit.server.util;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.gerrit.entities.AttentionSetUpdate;
|
||||||
|
import com.google.gerrit.entities.AttentionSetUpdate.Operation;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/** Common helpers for dealing with attention set data structures. */
|
||||||
|
public class AttentionSetUtil {
|
||||||
|
/** Returns only updates where the user was added. */
|
||||||
|
public static ImmutableSet<AttentionSetUpdate> additionsOnly(
|
||||||
|
Collection<AttentionSetUpdate> updates) {
|
||||||
|
return updates.stream()
|
||||||
|
.filter(u -> u.operation() == Operation.ADD)
|
||||||
|
.collect(ImmutableSet.toImmutableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttentionSetUtil() {}
|
||||||
|
}
|
@@ -54,6 +54,7 @@ import com.google.gerrit.entities.Project;
|
|||||||
import com.google.gerrit.entities.RefNames;
|
import com.google.gerrit.entities.RefNames;
|
||||||
import com.google.gerrit.extensions.api.GerritApi;
|
import com.google.gerrit.extensions.api.GerritApi;
|
||||||
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
||||||
|
import com.google.gerrit.extensions.api.changes.AddToAttentionSetInput;
|
||||||
import com.google.gerrit.extensions.api.changes.AssigneeInput;
|
import com.google.gerrit.extensions.api.changes.AssigneeInput;
|
||||||
import com.google.gerrit.extensions.api.changes.ChangeApi;
|
import com.google.gerrit.extensions.api.changes.ChangeApi;
|
||||||
import com.google.gerrit.extensions.api.changes.Changes.QueryRequest;
|
import com.google.gerrit.extensions.api.changes.Changes.QueryRequest;
|
||||||
@@ -2940,6 +2941,41 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
|
|||||||
mergedOwned);
|
mergedOwned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void attentionSetIndexed() throws Exception {
|
||||||
|
assume().that(getSchema().hasField(ChangeField.ATTENTION_SET_USERS)).isTrue();
|
||||||
|
TestRepository<Repo> repo = createProject("repo");
|
||||||
|
Change change1 = insert(repo, newChange(repo));
|
||||||
|
Change change2 = insert(repo, newChange(repo));
|
||||||
|
|
||||||
|
AddToAttentionSetInput input = new AddToAttentionSetInput(userId.toString(), "some reason");
|
||||||
|
gApi.changes().id(change1.getChangeId()).addToAttentionSet(input);
|
||||||
|
|
||||||
|
assertQuery("attention:" + user.getUserName().get(), change1);
|
||||||
|
assertQuery("-attention:" + userId.toString(), change2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void attentionSetStored() throws Exception {
|
||||||
|
assume().that(getSchema().hasField(ChangeField.ATTENTION_SET_USERS)).isTrue();
|
||||||
|
TestRepository<Repo> repo = createProject("repo");
|
||||||
|
Change change = insert(repo, newChange(repo));
|
||||||
|
|
||||||
|
AddToAttentionSetInput input = new AddToAttentionSetInput(userId.toString(), "reason 1");
|
||||||
|
gApi.changes().id(change.getChangeId()).addToAttentionSet(input);
|
||||||
|
Account.Id user2Id =
|
||||||
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
||||||
|
input = new AddToAttentionSetInput(user2Id.toString(), "reason 2");
|
||||||
|
gApi.changes().id(change.getChangeId()).addToAttentionSet(input);
|
||||||
|
|
||||||
|
List<ChangeInfo> result = newQuery("attention:" + user2Id.toString()).get();
|
||||||
|
assertThat(result).hasSize(1);
|
||||||
|
ChangeInfo changeInfo = Iterables.getOnlyElement(result);
|
||||||
|
assertThat(changeInfo.attentionSet.keySet()).containsExactly(userId.get(), user2Id.get());
|
||||||
|
assertThat(changeInfo.attentionSet.get(userId.get()).reason).isEqualTo("reason 1");
|
||||||
|
assertThat(changeInfo.attentionSet.get(user2Id.get()).reason).isEqualTo("reason 2");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void assignee() throws Exception {
|
public void assignee() throws Exception {
|
||||||
TestRepository<Repo> repo = createProject("repo");
|
TestRepository<Repo> repo = createProject("repo");
|
||||||
|
Reference in New Issue
Block a user