Merge "Reduce the cardinality of the submit requirements exported metrics"
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
index 1d24838..d67637e 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
@@ -32,30 +32,38 @@
   });
 
   test('renders', () => {
-    expect(element).shadowDom.to.equal(/* HTML */ `
-      <paper-input
-        aria-disabled="false"
-        autocomplete="off"
-        id="input"
-        tabindex="0"
-      >
-        <div slot="prefix">
-          <gr-icon icon="search" class="searchIcon"></gr-icon>
-        </div>
-        <div slot="suffix">
-          <slot name="suffix"> </slot>
-        </div>
-      </paper-input>
-      <gr-autocomplete-dropdown
-        horizontal-align="left"
-        id="suggestions"
-        is-hidden=""
-        role="listbox"
-        style="position: fixed; top: 300px; left: 392.5px; box-sizing: border-box; max-height: 600px; max-width: 785px;"
-        vertical-align="top"
-      >
-      </gr-autocomplete-dropdown>
-    `);
+    expect(element).shadowDom.to.equal(
+      /* HTML */ `
+        <paper-input
+          aria-disabled="false"
+          autocomplete="off"
+          id="input"
+          tabindex="0"
+        >
+          <div slot="prefix">
+            <gr-icon icon="search" class="searchIcon"></gr-icon>
+          </div>
+          <div slot="suffix">
+            <slot name="suffix"> </slot>
+          </div>
+        </paper-input>
+        <gr-autocomplete-dropdown
+          horizontal-align="left"
+          id="suggestions"
+          is-hidden=""
+          role="listbox"
+          style="position: fixed; top: 300px; left: 392.5px; box-sizing: border-box; max-height: 600px; max-width: 785px;"
+          vertical-align="top"
+        >
+        </gr-autocomplete-dropdown>
+      `,
+      {
+        // gr-autocomplete-dropdown sizing seems to vary between local & CI
+        ignoreAttributes: [
+          {tags: ['gr-autocomplete-dropdown'], attributes: ['style']},
+        ],
+      }
+    );
   });
 
   test('renders with suggestions', async () => {
diff --git a/polygerrit-ui/app/models/comments/comments-model.ts b/polygerrit-ui/app/models/comments/comments-model.ts
index f0d3be2..daf4d00 100644
--- a/polygerrit-ui/app/models/comments/comments-model.ts
+++ b/polygerrit-ui/app/models/comments/comments-model.ts
@@ -14,6 +14,7 @@
   PathToCommentsInfoMap,
   RobotCommentInfo,
   PathToRobotCommentsInfoMap,
+  AccountInfo,
 } from '../../types/common';
 import {
   addPath,
@@ -34,12 +35,13 @@
 import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
 import {ChangeModel} from '../change/change-model';
 import {Interaction, Timing} from '../../constants/reporting';
-import {assertIsDefined} from '../../utils/common-util';
+import {assertIsDefined, unique} from '../../utils/common-util';
 import {debounce, DelayedTask} from '../../utils/async-util';
 import {pluralize} from '../../utils/string-util';
 import {ReportingService} from '../../services/gr-reporting/gr-reporting';
 import {Model} from '../model';
 import {Deduping} from '../../api/reporting';
+import {extractMentionedUsers} from '../../utils/account-util';
 
 export interface CommentState {
   /** undefined means 'still loading' */
@@ -251,6 +253,15 @@
     commentState => commentState.discardedDrafts
   );
 
+  public readonly mentionedUsersInDrafts$ = select(this.drafts$, drafts => {
+    const users: AccountInfo[] = [];
+    const comments = Object.values(drafts ?? {}).flat();
+    for (const comment of comments) {
+      users.push(...extractMentionedUsers(comment.message));
+    }
+    return users.filter(unique);
+  });
+
   // Emits a new value even if only a single draft is changed. Components should
   // aim to subsribe to something more specific.
   public readonly changeComments$ = select(
diff --git a/polygerrit-ui/app/utils/account-util.ts b/polygerrit-ui/app/utils/account-util.ts
index 012dc1c..5967745 100644
--- a/polygerrit-ui/app/utils/account-util.ts
+++ b/polygerrit-ui/app/utils/account-util.ts
@@ -199,14 +199,16 @@
  * @ token which would have triggered the mentions dropdown and then looks
  * for the email token ending with a whitespace or end of string.
  */
-export function extractMentionedEmails(text?: string): EmailAddress[] {
+export function extractMentionedUsers(text?: string): AccountInfo[] {
   if (!text) return [];
   let match;
-  const emails = [];
+  const users = [];
   while ((match = MENTIONS_REGEX.exec(text))) {
-    emails.push(match[1] as EmailAddress);
+    users.push({
+      email: match[1] as EmailAddress,
+    });
   }
-  return emails;
+  return users;
 }
 
 export function toReviewInput(
diff --git a/polygerrit-ui/app/utils/account-util_test.ts b/polygerrit-ui/app/utils/account-util_test.ts
index 68fa386..c4f4701 100644
--- a/polygerrit-ui/app/utils/account-util_test.ts
+++ b/polygerrit-ui/app/utils/account-util_test.ts
@@ -6,7 +6,7 @@
 import '../test/common-test-setup-karma';
 import {
   computeVoteableText,
-  extractMentionedEmails,
+  extractMentionedUsers,
   getAccountTemplate,
   isServiceUser,
   removeServiceUsers,
@@ -17,7 +17,12 @@
   AccountTag,
   DefaultDisplayNameConfig,
 } from '../constants/constants';
-import {AccountId, AccountInfo, ServerInfo} from '../api/rest-api';
+import {
+  AccountId,
+  AccountInfo,
+  EmailAddress,
+  ServerInfo,
+} from '../api/rest-api';
 import {
   createAccountDetailWithId,
   createChange,
@@ -61,35 +66,41 @@
     assert.isTrue(isServiceUser(BOTTY));
   });
 
-  test('extractMentionedEmails', () => {
+  test('extractMentionedUsers', () => {
     let text =
       'Hi @kamilm@google.com and @brohlfs@google.com can you take a look at this?';
-    assert.deepEqual(extractMentionedEmails(text), [
-      'kamilm@google.com',
-      'brohlfs@google.com',
+    assert.deepEqual(extractMentionedUsers(text), [
+      {email: 'kamilm@google.com' as EmailAddress},
+      {email: 'brohlfs@google.com' as EmailAddress},
     ]);
 
     // with extra @
     text = '@@abc@google.com';
-    assert.deepEqual(extractMentionedEmails(text), []);
+    assert.deepEqual(extractMentionedUsers(text), []);
 
     // with spaces in email
     text = '@a bc@google.com';
-    assert.deepEqual(extractMentionedEmails(text), []);
+    assert.deepEqual(extractMentionedUsers(text), []);
 
     // with invalid email
     text = '@abcgoogle.com';
-    assert.deepEqual(extractMentionedEmails(text), []);
+    assert.deepEqual(extractMentionedUsers(text), []);
 
     text = '@abc@googlecom';
-    assert.deepEqual(extractMentionedEmails(text), ['abc@googlecom']);
+    assert.deepEqual(extractMentionedUsers(text), [
+      {email: 'abc@googlecom' as EmailAddress},
+    ]);
 
     // with newline before email
     text = '\n\n\n random text  \n\n@abc@google.com';
-    assert.deepEqual(extractMentionedEmails(text), ['abc@google.com']);
+    assert.deepEqual(extractMentionedUsers(text), [
+      {email: 'abc@google.com' as EmailAddress},
+    ]);
 
     text = '@abc@google.com please take a look at this';
-    assert.deepEqual(extractMentionedEmails(text), ['abc@google.com']);
+    assert.deepEqual(extractMentionedUsers(text), [
+      {email: 'abc@google.com' as EmailAddress},
+    ]);
   });
 
   test('removeServiceUsers', () => {