Merge "Better reporting of plugins that fail to load"
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
index ca6c689..f028def 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.MoreCollectors;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.LabelType;
 import com.google.gerrit.entities.SubmitRecord;
 import com.google.gerrit.entities.SubmitRecord.Label;
@@ -33,14 +34,20 @@
  * com.google.gerrit.entities.SubmitRequirementResult}s.
  */
 public class SubmitRequirementsAdapter {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
   private SubmitRequirementsAdapter() {}
 
   public static List<SubmitRequirementResult> createResult(
       SubmitRecord record, List<LabelType> labelTypes, ObjectId psCommitId) {
+    List<SubmitRequirementResult> results;
     if (record.ruleName.equals("gerrit~DefaultSubmitRule")) {
-      return createFromDefaultSubmitRecord(record.labels, labelTypes, psCommitId);
+      results = createFromDefaultSubmitRecord(record.labels, labelTypes, psCommitId);
+    } else {
+      results = createFromCustomSubmitRecord(record, psCommitId);
     }
-    return createFromCustomSubmitRecord(record, psCommitId);
+    logger.atFine().log("Converted submit record %s to submit requirements %s", record, results);
+    return results;
   }
 
   private static List<SubmitRequirementResult> createFromDefaultSubmitRecord(
@@ -107,7 +114,7 @@
               .submittabilityExpressionResult(
                   createExpressionResult(
                       sr.submittabilityExpression(),
-                      mapStatus(record),
+                      mapStatus(label),
                       ImmutableList.of(expressionString)))
               .patchSetCommitId(psCommitId)
               .build());
diff --git a/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java b/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
index f6c4d6a..05eb6e0 100644
--- a/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
+++ b/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
@@ -258,6 +258,34 @@
         SubmitRequirementExpressionResult.Status.FAIL);
   }
 
+  @Test
+  public void customSubmitRule_withMixOfPassingAndFailingLabels() {
+    SubmitRecord submitRecord =
+        createSubmitRecord(
+            "gerrit~PrologRule",
+            Status.NOT_READY,
+            Arrays.asList(
+                createLabel("custom-label-1", Label.Status.OK),
+                createLabel("custom-label-2", Label.Status.REJECT)));
+
+    List<SubmitRequirementResult> requirements =
+        SubmitRequirementsAdapter.createResult(submitRecord, labelTypes, psCommitId);
+
+    assertThat(requirements).hasSize(2);
+    assertResult(
+        requirements.get(0),
+        /* reqName= */ "custom-label-1",
+        /* submitExpression= */ "label:custom-label-1=gerrit~PrologRule",
+        SubmitRequirementResult.Status.SATISFIED,
+        SubmitRequirementExpressionResult.Status.PASS);
+    assertResult(
+        requirements.get(1),
+        /* reqName= */ "custom-label-2",
+        /* submitExpression= */ "label:custom-label-2=gerrit~PrologRule",
+        SubmitRequirementResult.Status.UNSATISFIED,
+        SubmitRequirementExpressionResult.Status.FAIL);
+  }
+
   private void assertResult(
       SubmitRequirementResult r,
       String reqName,
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
index e458611..fdd7c79 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -90,27 +90,19 @@
  */
 function computeThreads(
   message: CombinedMessage,
-  changeComments?: ChangeComments
+  allThreadsForChange: CommentThread[]
 ): CommentThread[] {
-  if (message._index === undefined || changeComments === undefined) {
+  if (message._index === undefined) {
     return [];
   }
   const messageId = getMessageId(message);
-  return changeComments.getAllThreadsForChange().filter(thread =>
-    thread.comments
-      .map(comment => {
-        // collapse all by default
-        comment.collapsed = true;
-        return comment;
-      })
-      .some(comment => {
-        const condition = comment.change_message_id === messageId;
-        // Since getAllThreadsForChange() always returns a new copy of
-        // all comments we can modify them here without worrying about
-        // polluting other threads.
-        comment.collapsed = !condition;
-        return condition;
-      })
+  return allThreadsForChange.filter(thread =>
+    thread.comments.some(comment => {
+      const matchesMessage = comment.change_message_id === messageId;
+      if (!matchesMessage) return false;
+      comment.collapsed = !matchesMessage;
+      return matchesMessage;
+    })
   );
 }
 
@@ -198,7 +190,6 @@
 }
 
 export const TEST_ONLY = {
-  computeThreads,
   computeTag,
   computeRevision,
   computeIsImportant,
@@ -355,14 +346,24 @@
         mDate = null;
       }
     }
-    combinedMessages.forEach(m => {
-      if (m.expanded === undefined) {
-        m.expanded = false;
+
+    const allThreadsForChange = changeComments.getAllThreadsForChange();
+    // collapse all by default
+    for (const thread of allThreadsForChange) {
+      for (const comment of thread.comments) {
+        comment.collapsed = true;
       }
-      m.commentThreads = computeThreads(m, changeComments);
-      m._revision_number = computeRevision(m, combinedMessages);
-      m.tag = computeTag(m);
-    });
+    }
+
+    for (let i = 0; i < combinedMessages.length; i++) {
+      const message = combinedMessages[i];
+      if (message.expanded === undefined) {
+        message.expanded = false;
+      }
+      message.commentThreads = computeThreads(message, allThreadsForChange);
+      message._revision_number = computeRevision(message, combinedMessages);
+      message.tag = computeTag(message);
+    }
     // computeIsImportant() depends on tags and revision numbers already being
     // updated for all messages, so we have to compute this in its own forEach
     // loop.
@@ -372,10 +373,6 @@
     return combinedMessages;
   }
 
-  getCommentThreads(message: CombinedMessage, changeComments?: ChangeComments) {
-    return computeThreads(message, changeComments);
-  }
-
   _updateExpandedStateOfAllMessages(exp: boolean) {
     if (this._combinedMessages) {
       for (let i = 0; i < this._combinedMessages.length; i++) {
@@ -441,24 +438,26 @@
   }
 
   /**
-   * This method is for reporting stats only.
+   * Called when this._combinedMessages has changed.
    */
   _combinedMessagesChanged(combinedMessages?: CombinedMessage[]) {
-    if (combinedMessages) {
-      if (combinedMessages.length === 0) return;
-      const tags = combinedMessages.map(
-        message =>
-          message.tag || (message as FormattedReviewerUpdateInfo).type || 'none'
-      );
-      const tagsCounted = tags.reduce(
-        (acc, val) => {
-          acc[val] = (acc[val] || 0) + 1;
-          return acc;
-        },
-        {all: combinedMessages.length} as TagsCountReportInfo
-      );
-      this.reporting.reportInteraction('messages-count', tagsCounted);
+    if (!combinedMessages) return;
+    if (combinedMessages.length === 0) return;
+    for (let i = 0; i < combinedMessages.length; i++) {
+      this.notifyPath(`_combinedMessages.${i}.commentThreads`);
     }
+    const tags = combinedMessages.map(
+      message =>
+        message.tag || (message as FormattedReviewerUpdateInfo).type || 'none'
+    );
+    const tagsCounted = tags.reduce(
+      (acc, val) => {
+        acc[val] = (acc[val] || 0) + 1;
+        return acc;
+      },
+      {all: combinedMessages.length} as TagsCountReportInfo
+    );
+    this.reporting.reportInteraction('messages-count', tagsCounted);
   }
 
   /**
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.ts
index 93df77e..56fae87 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.ts
@@ -93,7 +93,7 @@
       change="[[change]]"
       change-num="[[changeNum]]"
       message="[[message]]"
-      comment-threads="[[getCommentThreads(message, changeComments)]]"
+      comment-threads="[[message.commentThreads]]"
       project-name="[[projectName]]"
       show-reply-button="[[showReplyButtons]]"
       on-message-anchor-tap="_handleAnchorClick"