Merge "Add indexed field for total inline comment count"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 1829c6b..b1c9a88 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5769,8 +5769,12 @@
 Number of inserted lines.
 |`deletions`          ||
 Number of deleted lines.
+|`total_comment_count`  |optional|
+Total number of inline comments across all patch sets. Not set if the current
+change index doesn't have the data.
 |`unresolved_comment_count`  |optional|
-Number of unresolved comments. Not set if the current change index doesn't have the data.
+Number of unresolved inline comment threads across all patch sets. Not set if
+the current change index doesn't have the data.
 |`_number`            ||The legacy numeric ID of the change.
 |`owner`              ||
 The owner of the change as an link:rest-api-accounts.html#account-info[
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfo.java b/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 945c239..9a739ef 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -46,6 +46,7 @@
   public Boolean submittable;
   public Integer insertions;
   public Integer deletions;
+  public Integer totalCommentCount;
   public Integer unresolvedCommentCount;
   public Boolean isPrivate;
   public Boolean workInProgress;
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 66db468..b208a31 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -76,6 +76,7 @@
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
+import java.util.function.Consumer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexableField;
@@ -129,6 +130,7 @@
       ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName();
   private static final String SUBMIT_RECORD_STRICT_FIELD =
       ChangeField.STORED_SUBMIT_RECORD_STRICT.getName();
+  private static final String TOTAL_COMMENT_COUNT_FIELD = ChangeField.TOTAL_COMMENT_COUNT.getName();
   private static final String UNRESOLVED_COMMENT_COUNT_FIELD =
       ChangeField.UNRESOLVED_COMMENT_COUNT.getName();
 
@@ -504,6 +506,7 @@
     }
 
     decodeUnresolvedCommentCount(doc, cd);
+    decodeTotalCommentCount(doc, cd);
     return cd;
   }
 
@@ -633,9 +636,18 @@
 
   private void decodeUnresolvedCommentCount(
       ListMultimap<String, IndexableField> doc, ChangeData cd) {
-    IndexableField f = Iterables.getFirst(doc.get(UNRESOLVED_COMMENT_COUNT_FIELD), null);
+    decodeIntField(doc, UNRESOLVED_COMMENT_COUNT_FIELD, cd::setUnresolvedCommentCount);
+  }
+
+  private void decodeTotalCommentCount(ListMultimap<String, IndexableField> doc, ChangeData cd) {
+    decodeIntField(doc, TOTAL_COMMENT_COUNT_FIELD, cd::setTotalCommentCount);
+  }
+
+  private static void decodeIntField(
+      ListMultimap<String, IndexableField> doc, String fieldName, Consumer<Integer> consumer) {
+    IndexableField f = Iterables.getFirst(doc.get(fieldName), null);
     if (f != null && f.numericValue() != null) {
-      cd.setUnresolvedCommentCount(f.numericValue().intValue());
+      consumer.accept(f.numericValue().intValue());
     }
   }
 
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index c64cd12..b7049a7 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -535,6 +535,7 @@
     out.created = in.getCreatedOn();
     out.updated = in.getLastUpdatedOn();
     out._number = in.getId().get();
+    out.totalCommentCount = cd.totalCommentCount();
     out.unresolvedCommentCount = cd.unresolvedCommentCount();
 
     if (user.isIdentifiedUser()) {
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 5d12e79..b79a1c2 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -508,11 +508,15 @@
                           cd.messages().stream().map(ChangeMessage::getMessage))
                       .collect(toSet()));
 
-  /** Number of unresolved comments of the change. */
+  /** Number of unresolved comment threads of the change, including robot comments. */
   public static final FieldDef<ChangeData, Integer> UNRESOLVED_COMMENT_COUNT =
       intRange(ChangeQueryBuilder.FIELD_UNRESOLVED_COMMENT_COUNT)
           .build(ChangeData::unresolvedCommentCount);
 
+  /** Total number of published inline comments of the change, including robot comments. */
+  public static final FieldDef<ChangeData, Integer> TOTAL_COMMENT_COUNT =
+      intRange("total_comments").build(ChangeData::totalCommentCount);
+
   /** Whether the change is mergeable. */
   public static final FieldDef<ChangeData, String> MERGEABLE =
       exact(ChangeQueryBuilder.FIELD_MERGEABLE)
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 2000cd1..9016fd1 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -99,7 +99,9 @@
   @Deprecated static final Schema<ChangeData> V49 = schema(V48);
 
   // Bump Lucene version requires reindexing
-  static final Schema<ChangeData> V50 = schema(V49);
+  @Deprecated static final Schema<ChangeData> V50 = schema(V49);
+
+  static final Schema<ChangeData> V51 = schema(V50, ChangeField.TOTAL_COMMENT_COUNT);
 
   public static final String NAME = "changes";
   public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 83d68db..0f5d938 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -30,6 +30,7 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.primitives.Ints;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.SubmitRecord;
@@ -391,6 +392,7 @@
   private PersonIdent committer;
   private int parentCount;
   private Integer unresolvedCommentCount;
+  private Integer totalCommentCount;
   private LabelTypes labelTypes;
 
   private ImmutableList<byte[]> refStates;
@@ -925,6 +927,23 @@
     this.unresolvedCommentCount = count;
   }
 
+  public Integer totalCommentCount() throws OrmException {
+    if (totalCommentCount == null) {
+      if (!lazyLoad) {
+        return null;
+      }
+
+      // Fail on overflow.
+      totalCommentCount =
+          Ints.checkedCast((long) publishedComments().size() + robotComments().size());
+    }
+    return totalCommentCount;
+  }
+
+  public void setTotalCommentCount(Integer count) {
+    this.totalCommentCount = count;
+  }
+
   public List<ChangeMessage> messages() throws OrmException {
     if (messages == null) {
       if (!lazyLoad) {
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index cd20765..d713db6 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -1024,7 +1024,7 @@
   }
 
   @Test
-  public void queryChangesWithUnresolvedCommentCount() throws Exception {
+  public void queryChangesWithCommentCounts() throws Exception {
     assume().that(notesMigration.readChanges()).isTrue();
 
     PushOneCommit.Result r1 = createChange();
@@ -1043,6 +1043,7 @@
       // if we allow users to resolve a robot comment, then this test should
       // be modified.
       assertThat(result.unresolvedCommentCount).isEqualTo(0);
+      assertThat(result.totalCommentCount).isEqualTo(1);
     } finally {
       enableDb(ctx);
     }
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 8b1f4bc..0d40a1c 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -716,7 +716,7 @@
   }
 
   @Test
-  public void queryChangesWithUnresolvedCommentCount() throws Exception {
+  public void queryChangesWithCommentCount() throws Exception {
     // PS1 has three comments in three different threads, PS2 has one comment in one thread.
     PushOneCommit.Result result = createChange("change 1", FILE_NAME, "content 1");
     String changeId1 = result.getChangeId();
@@ -751,8 +751,11 @@
       ChangeInfo changeInfo2 = Iterables.getOnlyElement(query(changeId2));
       ChangeInfo changeInfo3 = Iterables.getOnlyElement(query(changeId3));
       assertThat(changeInfo1.unresolvedCommentCount).isEqualTo(2);
+      assertThat(changeInfo1.totalCommentCount).isEqualTo(4);
       assertThat(changeInfo2.unresolvedCommentCount).isEqualTo(0);
+      assertThat(changeInfo2.totalCommentCount).isEqualTo(2);
       assertThat(changeInfo3.unresolvedCommentCount).isEqualTo(1);
+      assertThat(changeInfo3.totalCommentCount).isEqualTo(2);
     } finally {
       enableDb(ctx);
     }
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index b9973e9..f362e5a 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -2378,6 +2378,12 @@
     cd.reviewers();
     cd.unresolvedCommentCount();
 
+    if (getSchemaVersion() < 51) {
+      assertMissingField(ChangeField.TOTAL_COMMENT_COUNT);
+    } else {
+      cd.totalCommentCount();
+    }
+
     // TODO(dborowitz): Swap out GitRepositoryManager somehow? Will probably be
     // necessary for NoteDb anyway.
     cd.isMergeable();