Reuse account details that are part of the change

When file owners are returned from plugin they contain only the
`_account_id` however there is a chance that some account details are
already available in change (e.g. in reviewers set). Consult change
details in order to avoid costly server round-trip.

The following change's attributes are considered as account details
sources:
* owner
* submitter
* reviewers (both regular and CC)

Bug: Issue 373151160
Change-Id: I0a43793b15b8f1cfd193bcbae2bf436b3f13a7f0
diff --git a/owners/web/gr-owners.ts b/owners/web/gr-owners.ts
index e60e5d4..2c9eeb4 100644
--- a/owners/web/gr-owners.ts
+++ b/owners/web/gr-owners.ts
@@ -26,6 +26,7 @@
   LabelInfo,
   isDetailedLabelInfo,
   EmailAddress,
+  ReviewerState,
 } from '@gerritcodereview/typescript-api/rest-api';
 import {
   FilesOwners,
@@ -427,6 +428,7 @@
                     .owner=${owner}
                     .approval=${approval}
                     .info=${info}
+                    .email=${owner.email}
                   ></gr-owner>
                 </div>
               `;
@@ -469,10 +471,15 @@
     }
 
     this.fileStatus = FILE_STATUS[fileOwnership.fileStatus];
+    const accounts = getChangeAccounts(this.change);
+
     // TODO for the time being filter out or group owners - to be decided what/how to display them
     this.owners = (fileOwnership.owners ?? [])
       .filter(isOwner)
-      .map(o => ({_account_id: o.id} as unknown as AccountInfo));
+      .map(
+        o =>
+          accounts.get(o.id) ?? ({_account_id: o.id} as unknown as AccountInfo)
+      );
   }
 }
 
@@ -566,3 +573,20 @@
 
   return;
 }
+
+export function getChangeAccounts(
+  change?: ChangeInfo
+): Map<number, AccountInfo> {
+  const accounts = new Map();
+  if (!change) {
+    return accounts;
+  }
+
+  [
+    change.owner,
+    ...(change.submitter ? [change.submitter] : []),
+    ...(change.reviewers[ReviewerState.REVIEWER] ?? []),
+    ...(change.reviewers[ReviewerState.CC] ?? []),
+  ].forEach(account => accounts.set(account._account_id, account));
+  return accounts;
+}
diff --git a/owners/web/gr-owners_test.ts b/owners/web/gr-owners_test.ts
index 4574c3e..43f199d 100644
--- a/owners/web/gr-owners_test.ts
+++ b/owners/web/gr-owners_test.ts
@@ -19,6 +19,7 @@
 
 import {
   computeApprovalAndInfo,
+  getChangeAccounts,
   getFileOwnership,
   shouldHide,
 } from './gr-owners';
@@ -30,6 +31,7 @@
   ChangeStatus,
   DetailedLabelInfo,
   SubmitRequirementResultInfo,
+  ReviewerState,
 } from '@gerritcodereview/typescript-api/rest-api';
 import {FilesOwners, OwnersLabels} from './owners-service';
 import {deepEqual} from './utils';
@@ -352,9 +354,61 @@
       );
     });
   });
+
+  suite('getChangeAccounts tests', () => {
+    test('getChangeAccounts - should return empty map when change is `undefined', () => {
+      const undefinedChange = undefined;
+      assert.equal(getChangeAccounts(undefinedChange).size, 0);
+    });
+
+    test('getChangeAccounts - should return map with owner when change has only owner and empty reviewers defined', () => {
+      const owner = account(1);
+      const changeWithOwner = {
+        owner,
+        reviewers: {},
+      } as unknown as ChangeInfo;
+      assert.equal(
+        deepEqual(getChangeAccounts(changeWithOwner), new Map([[1, owner]])),
+        true
+      );
+    });
+
+    test('getChangeAccounts - should return map with owner, submitter and reviewers', () => {
+      const owner = account(1);
+      const submitter = account(2);
+      const reviewer = account(3);
+      const ccReviewer = account(4);
+      const change = {
+        owner,
+        submitter,
+        reviewers: {
+          [ReviewerState.REVIEWER]: [reviewer],
+          [ReviewerState.CC]: [ccReviewer],
+        },
+      } as unknown as ChangeInfo;
+      assert.equal(
+        deepEqual(
+          getChangeAccounts(change),
+          new Map([
+            [1, owner],
+            [2, submitter],
+            [3, reviewer],
+            [4, ccReviewer],
+          ])
+        ),
+        true
+      );
+    });
+  });
 });
 
 function getRandom<T>(...values: T[]): T {
   const idx = Math.floor(Math.random() * values.length);
   return values[idx];
 }
+
+function account(id: number) {
+  return {
+    _account_id: id,
+  } as unknown as AccountInfo;
+}