Merge "Fix Error Prone warnings"
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 20efa7a..3ddcae4 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -324,6 +324,9 @@
   mentionedUsers: AccountInput[] = [];
 
   @state()
+  mentionedUsersInUnresolvedDrafts: AccountInfo[] = [];
+
+  @state()
   attentionCcsCount = 0;
 
   @state()
@@ -406,8 +409,6 @@
 
   private readonly accountsModel = getAppContext().accountsModel;
 
-  private mentionedUsersInUnresolvedDrafts: AccountInfo[] = [];
-
   private latestPatchNum?: PatchSetNumber;
 
   storeTask?: DelayedTask;
@@ -808,6 +809,7 @@
       changedProperties.has('ccs') ||
       changedProperties.has('change') ||
       changedProperties.has('draftCommentThreads') ||
+      changedProperties.has('mentionedUsersInUnresolvedDrafts') ||
       changedProperties.has('includeComments') ||
       changedProperties.has('labelsChanged') ||
       changedProperties.has('patchsetLevelDraftMessage') ||
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
index fda4016..a6f33fa 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -50,6 +50,7 @@
   ReviewResult,
   RevisionPatchSetNum,
   Suggestion,
+  Timestamp,
   UrlEncodedCommentId,
   UserId,
 } from '../../../types/common';
@@ -2583,6 +2584,12 @@
     });
 
     test('mentioned user in unresolved draft is added to CC and AttentionSet', async () => {
+      stubRestApi('getAccountDetails').returns(
+        Promise.resolve({
+          ...createAccountWithEmail('abcd@def.com' as EmailAddress),
+          registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+        })
+      );
       const draft = {
         ...createDraft(),
         message: 'hey @abcd@def.com take a look at this',
@@ -2599,7 +2606,9 @@
         discardedDrafts: [],
       });
       element.draftCommentThreads = [createCommentThread([draft])];
-      await waitUntil(() => element.mentionedUsers.length > 0);
+      await waitUntil(
+        () => element.mentionedUsersInUnresolvedDrafts.length > 0
+      );
 
       await element.updateComplete;
 
@@ -2622,6 +2631,12 @@
     });
 
     test('mention user can be manually removed from attention set', async () => {
+      stubRestApi('getAccountDetails').returns(
+        Promise.resolve({
+          ...createAccountWithEmail('abcd@def.com' as EmailAddress),
+          registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+        })
+      );
       const draft = {
         ...createDraft(),
         message: 'hey @abcd@def.com take a look at this',
@@ -2638,7 +2653,9 @@
         discardedDrafts: [],
       });
       element.draftCommentThreads = [createCommentThread([draft])];
-      await waitUntil(() => element.mentionedUsers.length > 0);
+      await waitUntil(
+        () => element.mentionedUsersInUnresolvedDrafts.length > 0
+      );
 
       await element.updateComplete;
 
diff --git a/polygerrit-ui/app/models/accounts-model/accounts-model.ts b/polygerrit-ui/app/models/accounts-model/accounts-model.ts
index d29d30e..74eb813 100644
--- a/polygerrit-ui/app/models/accounts-model/accounts-model.ts
+++ b/polygerrit-ui/app/models/accounts-model/accounts-model.ts
@@ -9,12 +9,15 @@
 import {Finalizable} from '../../services/registry';
 import {UserId} from '../../types/common';
 import {getUserId, isDetailedAccount} from '../../utils/account-util';
+import {define} from '../dependency';
 import {Model} from '../model';
 
 export interface AccountsState {
   accounts: {[id: UserId]: AccountDetailInfo};
 }
 
+export const accountsModelToken = define<AccountsModel>('accounts-model');
+
 export class AccountsModel extends Model<AccountsState> implements Finalizable {
   constructor(readonly restApiService: RestApiService) {
     super({
diff --git a/polygerrit-ui/app/models/comments/comments-model.ts b/polygerrit-ui/app/models/comments/comments-model.ts
index 5137407..3372b26 100644
--- a/polygerrit-ui/app/models/comments/comments-model.ts
+++ b/polygerrit-ui/app/models/comments/comments-model.ts
@@ -30,7 +30,7 @@
 import {RouterModel} from '../../services/router/router-model';
 import {Finalizable} from '../../services/registry';
 import {define} from '../dependency';
-import {combineLatest, Subscription} from 'rxjs';
+import {combineLatest, forkJoin, from, Observable, Subscription} from 'rxjs';
 import {fire, fireAlert, fireEvent} from '../../utils/event-util';
 import {CURRENT} from '../../utils/patch-set-util';
 import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
@@ -45,6 +45,14 @@
 import {extractMentionedUsers, getUserId} from '../../utils/account-util';
 import {EventType} from '../../types/events';
 import {SpecialFilePath} from '../../constants/constants';
+import {AccountsModel} from '../accounts-model/accounts-model';
+import {
+  distinctUntilChanged,
+  map,
+  shareReplay,
+  switchMap,
+} from 'rxjs/operators';
+import {notUndefined} from '../../types/types';
 
 export interface CommentState {
   /** undefined means 'still loading' */
@@ -283,22 +291,28 @@
     );
   });
 
-  public readonly mentionedUsersInUnresolvedDrafts$ = select(
-    this.drafts$,
-    drafts => {
-      const users: AccountInfo[] = [];
-      const comments = Object.values(drafts ?? {})
-        .flat()
-        .filter(c => c.unresolved);
-      for (const comment of comments) {
-        users.push(...extractMentionedUsers(comment.message));
-      }
-      return users.filter(
-        (user, index) =>
-          index === users.findIndex(u => getUserId(u) === getUserId(user))
-      );
-    }
-  );
+  public readonly mentionedUsersInUnresolvedDrafts$: Observable<AccountInfo[]> =
+    this.drafts$.pipe(
+      switchMap(drafts => {
+        const users: AccountInfo[] = [];
+        const comments = Object.values(drafts ?? {})
+          .flat()
+          .filter(c => c.unresolved);
+        for (const comment of comments) {
+          users.push(...extractMentionedUsers(comment.message));
+        }
+        const uniqueUsers = users.filter(
+          (user, index) =>
+            index === users.findIndex(u => getUserId(u) === getUserId(user))
+        );
+        const filledUsers$: Observable<AccountInfo | undefined>[] =
+          uniqueUsers.map(user => from(this.accountsModel.fillDetails(user)));
+        return forkJoin(filledUsers$);
+      }),
+      map(users => users.filter(notUndefined)),
+      distinctUntilChanged(deepEqual),
+      shareReplay(1)
+    );
 
   // Emits a new value even if only a single draft is changed. Components should
   // aim to subsribe to something more specific.
@@ -358,6 +372,7 @@
   constructor(
     readonly routerModel: RouterModel,
     readonly changeModel: ChangeModel,
+    readonly accountsModel: AccountsModel,
     readonly restApiService: RestApiService,
     readonly reporting: ReportingService
   ) {
diff --git a/polygerrit-ui/app/models/comments/comments-model_test.ts b/polygerrit-ui/app/models/comments/comments-model_test.ts
index 9a4a69d..ba7bd2d 100644
--- a/polygerrit-ui/app/models/comments/comments-model_test.ts
+++ b/polygerrit-ui/app/models/comments/comments-model_test.ts
@@ -67,6 +67,7 @@
     const model = new CommentsModel(
       getAppContext().routerModel,
       testResolver(changeModelToken),
+      getAppContext().accountsModel,
       getAppContext().restApiService,
       getAppContext().reportingService
     );
@@ -122,6 +123,7 @@
     const model = new CommentsModel(
       getAppContext().routerModel,
       testResolver(changeModelToken),
+      getAppContext().accountsModel,
       getAppContext().restApiService,
       getAppContext().reportingService
     );
diff --git a/polygerrit-ui/app/services/app-context-init.ts b/polygerrit-ui/app/services/app-context-init.ts
index 244e94f..2dca9a9 100644
--- a/polygerrit-ui/app/services/app-context-init.ts
+++ b/polygerrit-ui/app/services/app-context-init.ts
@@ -92,9 +92,12 @@
   );
   dependencies.set(changeModelToken, changeModel);
 
+  const accountsModel = new AccountsModel(appContext.restApiService);
+
   const commentsModel = new CommentsModel(
     appContext.routerModel,
     changeModel,
+    accountsModel,
     appContext.restApiService,
     appContext.reportingService
   );
diff --git a/polygerrit-ui/app/test/test-app-context-init.ts b/polygerrit-ui/app/test/test-app-context-init.ts
index 5bbec45..15857c8 100644
--- a/polygerrit-ui/app/test/test-app-context-init.ts
+++ b/polygerrit-ui/app/test/test-app-context-init.ts
@@ -33,7 +33,10 @@
 import {BrowserModel, browserModelToken} from '../models/browser/browser-model';
 import {PluginsModel} from '../models/plugins/plugins-model';
 import {MockHighlightService} from '../services/highlight/highlight-service-mock';
-import {AccountsModel} from '../models/accounts-model/accounts-model';
+import {
+  AccountsModel,
+  accountsModelToken,
+} from '../models/accounts-model/accounts-model';
 
 export function createTestAppContext(): AppContext & Finalizable {
   const appRegistry: Registry<AppContext> = {
@@ -97,10 +100,15 @@
     );
   dependencies.set(changeModelToken, changeModelCreator);
 
+  const accountsModelCreator = () =>
+    new AccountsModel(appContext.restApiService);
+  dependencies.set(accountsModelToken, accountsModelCreator);
+
   const commentsModelCreator = () =>
     new CommentsModel(
       appContext.routerModel,
       resolver(changeModelToken),
+      resolver(accountsModelToken),
       appContext.restApiService,
       appContext.reportingService
     );