Request change-details as part of sendReview request.

We still need to reload the comments because otherwise the latest
comment won't show up properly in the messages list.

Google-Bug-Id: b/296175151
Release-Notes: skip
Change-Id: I773747febd139496c02ccb781a44ed992a614803
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
index 8a5bf47..c65371b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
@@ -37,6 +37,7 @@
 import {ProgressStatus} from '../../../constants/constants';
 import {StandardLabels} from '../../../utils/label-util';
 import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
+import {ReviewResult} from '../../../types/common';
 
 const change1: ChangeInfo = {
   ...createChange(),
@@ -208,7 +209,7 @@
     );
     stubRestApi('saveChangeReview').callsFake(
       (_changeNum, _patchNum, _review, errFn) =>
-        Promise.resolve(new Response()).then(res => {
+        Promise.resolve(undefined).then(res => {
           errFn && errFn();
           return res;
         })
@@ -359,7 +360,7 @@
     );
     await selectChange(change);
     await element.updateComplete;
-    const saveChangeReview = mockPromise<Response>();
+    const saveChangeReview = mockPromise<ReviewResult>();
     stubRestApi('saveChangeReview').returns(saveChangeReview);
 
     queryAndAssert<GrButton>(element, '#voteFlowButton').click();
@@ -411,7 +412,7 @@
       ProgressStatus.RUNNING
     );
 
-    saveChangeReview.resolve({...new Response(), status: 200});
+    saveChangeReview.resolve({});
     await waitUntil(
       () =>
         element.progressByChange.get(1 as NumericChangeId) ===
@@ -445,7 +446,7 @@
 
       stubRestApi('saveChangeReview').callsFake(
         (_changeNum, _patchNum, _review, errFn) =>
-          Promise.resolve(new Response()).then(res => {
+          Promise.resolve({}).then(res => {
             errFn && errFn();
             return res;
           })
@@ -503,7 +504,7 @@
 
       stubRestApi('saveChangeReview').callsFake(
         (_changeNum, _patchNum, _review, errFn) =>
-          Promise.resolve(new Response()).then(res => {
+          Promise.resolve(undefined).then(res => {
             errFn && errFn();
             return res;
           })
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index fae8203..57bb875 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -25,7 +25,6 @@
 import {
   changeIsOpen,
   isOwner,
-  ListChangesOption,
   listChangesOptionsToHex,
 } from '../../../utils/change-util';
 import {
@@ -48,6 +47,7 @@
   isDetailedLabelInfo,
   isQuickLabelInfo,
   LabelInfo,
+  ListChangesOption,
   NumericChangeId,
   PatchSetNumber,
   RequestPayload,
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index 623f2d7..fd4bf73 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -711,7 +711,7 @@
         )
         .returns(review);
       const saveStub = stubRestApi('saveChangeReview').returns(
-        Promise.resolve(new Response())
+        Promise.resolve({})
       );
       const setReviewOnRevert = element.setReviewOnRevert(changeId) as Promise<
         undefined | Response
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index c3a7e5d..2276aa4 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -1874,7 +1874,7 @@
   handleReplySent() {
     assertIsDefined(this.replyModal);
     this.replyModal.close();
-    this.getChangeModel().navigateToChangeResetReload();
+    this.getCommentsModel().reloadAllComments();
   }
 
   private handleReplyCancel() {
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 8fd619d..fa44e18 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
@@ -58,6 +58,7 @@
   Suggestion,
   UserId,
   isDraft,
+  ChangeViewChangeInfo,
 } from '../../../types/common';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {GrLabelScores} from '../gr-label-scores/gr-label-scores';
@@ -130,6 +131,7 @@
 import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
 import {modalStyles} from '../../../styles/gr-modal-styles';
 import {ironAnnouncerRequestAvailability} from '../../polymer-util';
+import {GrReviewerUpdatesParser} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 
 export enum FocusTarget {
   ANY = 'any',
@@ -1427,26 +1429,18 @@
 
     const errFn = (r?: Response | null) => this.handle400Error(r);
     return this.saveReview(reviewInput, errFn)
-      .then(response => {
-        if (!response) {
-          // Null or undefined response indicates that an error handler
-          // took responsibility, so just return.
-          return;
-        }
-        if (!response.ok) {
-          fireServerError(response);
-          return;
-        }
+      .then(result => {
+        this.getChangeModel().updateStateChange(
+          GrReviewerUpdatesParser.parse(
+            result?.change_info as ChangeViewChangeInfo
+          )
+        );
 
         this.patchsetLevelDraftMessage = '';
         this.includeComments = true;
         fireNoBubble(this, 'send', {});
         fireIronAnnounce(this, 'Reply sent');
       })
-      .then(result => result)
-      .catch(err => {
-        throw err;
-      })
       .finally(() => {
         this.disabled = false;
         if (this.patchsetLevelGrComment) {
@@ -1849,7 +1843,8 @@
       this.change._number,
       this.latestPatchNum,
       review,
-      errFn
+      errFn,
+      true
     );
   }
 
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 8e294cf..3c3c246 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
@@ -21,7 +21,6 @@
   DraftsAction,
   ReviewerState,
 } from '../../../constants/constants';
-import {JSON_PREFIX} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
 import {StandardLabels} from '../../../utils/label-util';
 import {
   createAccountWithEmail,
@@ -170,14 +169,7 @@
         new Promise((resolve, reject) => {
           try {
             const result = jsonResponseProducer(review) || {};
-            const resultStr = JSON_PREFIX + JSON.stringify(result);
-            resolve({
-              ...new Response(),
-              ok: true,
-              text() {
-                return Promise.resolve(resultStr);
-              },
-            });
+            resolve(result);
           } catch (err) {
             reject(err);
           }
@@ -1522,62 +1514,6 @@
     assert.isTrue(element.reviewersMutated);
   });
 
-  test('400 converts to human-readable server-error', async () => {
-    stubRestApi('saveChangeReview').callsFake(
-      (_changeNum, _patchNum, _review, errFn) => {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-        errFn!(
-          cloneableResponse(
-            400,
-            '....{"reviewers":{"id1":{"error":"human readable"}}}'
-          ) as Response
-        );
-        return Promise.resolve(new Response());
-      }
-    );
-
-    const promise = mockPromise();
-    const listener = (event: Event) => {
-      if (event.target !== document) return;
-      (event as CustomEvent).detail.response.text().then((body: string) => {
-        if (body === 'human readable') {
-          promise.resolve();
-        }
-      });
-    };
-    addListenerForTest(document, 'server-error', listener);
-
-    await element.updateComplete;
-    element.send(false, false);
-    await promise;
-  });
-
-  test('non-json 400 is treated as a normal server-error', async () => {
-    stubRestApi('saveChangeReview').callsFake(
-      (_changeNum, _patchNum, _review, errFn) => {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-        errFn!(cloneableResponse(400, 'Comment validation error!') as Response);
-        return Promise.resolve(new Response());
-      }
-    );
-    const promise = mockPromise();
-    const listener = (event: Event) => {
-      if (event.target !== document) return;
-      (event as CustomEvent).detail.response.text().then((body: string) => {
-        if (body === 'Comment validation error!') {
-          promise.resolve();
-        }
-      });
-    };
-    addListenerForTest(document, 'server-error', listener);
-
-    // Async tick is needed because iron-selector content is distributed and
-    // distributed content requires an observer to be set up.
-    await element.updateComplete;
-    element.send(false, false);
-    await promise;
-  });
-
   test('filterReviewerSuggestion', () => {
     const owner = makeAccount();
     const reviewer1 = makeAccount();
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
index c2b6a33..ac6fa57 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
@@ -438,7 +438,7 @@
     this.restApiService
       .saveChangeReview(this.change._number, CURRENT, reviewInput)
       .then(response => {
-        if (!response || !response.ok) {
+        if (!response) {
           throw new Error(
             'something went wrong when toggling' +
               this.getReviewerState(this.change!)
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts
index 7df06f4..c3c48cb 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts
@@ -229,7 +229,7 @@
     };
     await element.updateComplete;
     const saveReviewStub = stubRestApi('saveChangeReview').returns(
-      Promise.resolve({...new Response(), ok: true})
+      Promise.resolve({})
     );
     stubRestApi('removeChangeReviewer').returns(
       Promise.resolve({...new Response(), ok: true})
@@ -257,7 +257,7 @@
     };
     await element.updateComplete;
     const saveReviewStub = stubRestApi('saveChangeReview').returns(
-      Promise.resolve({...new Response(), ok: true})
+      Promise.resolve({})
     );
     stubRestApi('removeChangeReviewer').returns(
       Promise.resolve({...new Response(), ok: true})
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
index b13a16f..e82c262 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
@@ -22,6 +22,7 @@
   ReviewerInput,
   AttentionSetInput,
   RelatedChangeAndCommitInfo,
+  ReviewResult,
 } from '../../types/common';
 import {getUserId} from '../../utils/account-util';
 import {getChangeNumber} from '../../utils/change-util';
@@ -164,7 +165,7 @@
   addReviewers(
     changedReviewers: Map<ReviewerState, (AccountInfo | GroupInfo)[]>,
     reason: string
-  ): Promise<Response>[] {
+  ): Promise<ReviewResult | undefined>[] {
     const current = this.getState();
     const changes = current.selectedChangeNums.map(
       changeNum => current.allChanges.get(changeNum)!
@@ -177,7 +178,7 @@
         this.getNewReviewersToChange(change, state, changedReviewers)
       );
       if (reviewersNewToChange.length === 0) {
-        return Promise.resolve(new Response());
+        return Promise.resolve(undefined);
       }
       const attentionSetUpdates: AttentionSetInput[] = reviewersNewToChange
         .filter(reviewerInput => reviewerInput.state === ReviewerState.REVIEWER)
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
index e2f75f7..ece126c 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
@@ -261,9 +261,7 @@
     let saveChangeReviewStub: sinon.SinonStub;
 
     setup(async () => {
-      saveChangeReviewStub = stubRestApi('saveChangeReview').resolves(
-        new Response()
-      );
+      saveChangeReviewStub = stubRestApi('saveChangeReview').resolves({});
       stubRestApi('getDetailedChangesWithActions').resolves([
         {...changes[0], actions: {abandon: {method: HttpMethod.POST}}},
         {...changes[1], status: ChangeStatus.ABANDONED},
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
index 11b9ccb..414548d 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
@@ -22,10 +22,7 @@
 import {getBaseUrl} from '../../utils/url-util';
 import {Finalizable} from '../registry';
 import {getParentIndex, isMergeParent} from '../../utils/patch-set-util';
-import {
-  ListChangesOption,
-  listChangesOptionsToHex,
-} from '../../utils/change-util';
+import {listChangesOptionsToHex} from '../../utils/change-util';
 import {assertNever, hasOwnProperty} from '../../utils/common-util';
 import {AuthService} from '../gr-auth/gr-auth';
 import {
@@ -119,6 +116,8 @@
   UrlEncodedCommentId,
   FixReplacementInfo,
   DraftInfo,
+  ListChangesOption,
+  ReviewResult,
 } from '../../types/common';
 import {
   DiffInfo,
@@ -1189,7 +1188,9 @@
     cancelCondition?: CancelConditionCallback
   ): Promise<ParsedChangeInfo | undefined> {
     if (!changeNum) return;
-    const optionsHex = await this.getChangeOptionsHex();
+    const optionsHex = listChangesOptionsToHex(
+      ...(await this.getChangeOptions())
+    );
 
     return this._getChangeDetail(
       changeNum,
@@ -1218,15 +1219,17 @@
     return listChangesOptionsToHex(...options);
   }
 
-  async getChangeOptionsHex(): Promise<string> {
+  async getChangeOptions(): Promise<number[]> {
     const config = await this.getConfig(false);
 
     // This list MUST be kept in sync with
     // ChangeIT#changeDetailsDoesNotRequireIndex
+    // This list MUST be kept in sync with getResponseFormatOptions
     const options = [
       ListChangesOption.ALL_COMMITS,
       ListChangesOption.ALL_REVISIONS,
       ListChangesOption.CHANGE_ACTIONS,
+      ListChangesOption.DETAILED_ACCOUNTS,
       ListChangesOption.DETAILED_LABELS,
       ListChangesOption.DOWNLOAD_COMMANDS,
       ListChangesOption.MESSAGES,
@@ -1238,7 +1241,32 @@
     if (config?.receive?.enable_signed_push) {
       options.push(ListChangesOption.PUSH_CERTIFICATES);
     }
-    return listChangesOptionsToHex(...options);
+    return options;
+  }
+
+  async getResponseFormatOptions(): Promise<string[]> {
+    const config = await this.getConfig(false);
+
+    // This list MUST be kept in sync with
+    // ChangeIT#changeDetailsDoesNotRequireIndex
+    // This list MUST be kept in sync with getChangeOptions
+    const options = [
+      'ALL_COMMITS',
+      'ALL_REVISIONS',
+      'CHANGE_ACTIONS',
+      'DETAILED_LABELS',
+      'DETAILED_ACCOUNTS',
+      'DOWNLOAD_COMMANDS',
+      'MESSAGES',
+      'SUBMITTABLE',
+      'WEB_LINKS',
+      'SKIP_DIFFSTAT',
+      'SUBMIT_REQUIREMENTS',
+    ];
+    if (config?.receive?.enable_signed_push) {
+      options.push('PUSH_CERTIFICATES');
+    }
+    return options;
   }
 
   /**
@@ -1947,37 +1975,36 @@
     });
   }
 
-  saveChangeReview(
-    changeNum: NumericChangeId,
-    patchNum: PatchSetNum,
-    review: ReviewInput
-  ): Promise<Response>;
-
-  saveChangeReview(
+  async saveChangeReview(
     changeNum: NumericChangeId,
     patchNum: PatchSetNum,
     review: ReviewInput,
-    errFn: ErrorCallback
-  ): Promise<Response | undefined>;
-
-  saveChangeReview(
-    changeNum: NumericChangeId,
-    patchNum: PatchSetNum,
-    review: ReviewInput,
-    errFn?: ErrorCallback
+    errFn?: ErrorCallback,
+    fetchDetail?: boolean
   ) {
+    if (fetchDetail) {
+      review.response_format_options = await this.getResponseFormatOptions();
+    }
     const promises: [Promise<void>, Promise<string>] = [
       this.awaitPendingDiffDrafts(),
       this.getChangeActionURL(changeNum, patchNum, '/review'),
     ];
-    return Promise.all(promises).then(([, url]) =>
-      this._restApiHelper.send({
-        method: HttpMethod.POST,
-        url,
-        body: review,
-        errFn,
-      })
-    );
+    return Promise.all(promises)
+      .then(([, url]) =>
+        this._restApiHelper.send({
+          method: HttpMethod.POST,
+          url,
+          body: review,
+          errFn,
+          parseResponse: true,
+        })
+      )
+      .then(payload => {
+        if (!payload) {
+          return undefined;
+        }
+        return payload as unknown as ReviewResult;
+      });
   }
 
   getChangeEdit(changeNum?: NumericChangeId): Promise<EditInfo | undefined> {
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
index 696e142..8b7e3bd 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
@@ -12,10 +12,7 @@
   waitEventLoop,
 } from '../../test/test-utils';
 import {GrReviewerUpdatesParser} from '../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
-import {
-  ListChangesOption,
-  listChangesOptionsToHex,
-} from '../../utils/change-util';
+import {listChangesOptionsToHex} from '../../utils/change-util';
 import {
   createAccountDetailWithId,
   createChange,
@@ -47,6 +44,7 @@
   EditPreferencesInfo,
   Hashtag,
   HashtagsInput,
+  ListChangesOption,
   NumericChangeId,
   PARENT,
   ParsedJSON,
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
index 814d746..1bf5c90 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
@@ -91,6 +91,7 @@
   UrlEncodedCommentId,
   UserId,
   DraftInfo,
+  ReviewResult,
 } from '../../types/common';
 import {
   DiffInfo,
@@ -352,20 +353,10 @@
   saveChangeReview(
     changeNum: ChangeId | NumericChangeId,
     patchNum: RevisionId,
-    review: ReviewInput
-  ): Promise<Response>;
-  saveChangeReview(
-    changeNum: ChangeId | NumericChangeId,
-    patchNum: RevisionId,
     review: ReviewInput,
-    errFn: ErrorCallback
-  ): Promise<Response | undefined>;
-  saveChangeReview(
-    changeNum: ChangeId | NumericChangeId,
-    patchNum: RevisionId,
-    review: ReviewInput,
-    errFn?: ErrorCallback
-  ): Promise<Response>;
+    errFn?: ErrorCallback,
+    fetch_detail?: boolean
+  ): Promise<ReviewResult | undefined>;
 
   getChangeEdit(changeNum?: NumericChangeId): Promise<EditInfo | undefined>;
 
diff --git a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
index cd0c2dd..531697d 100644
--- a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
+++ b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
@@ -481,7 +481,7 @@
     return Promise.resolve(new Response());
   },
   saveChangeReview() {
-    return Promise.resolve(new Response());
+    return Promise.resolve({});
   },
   saveChangeStarred(): Promise<Response> {
     return Promise.resolve(new Response());
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 6c3041f..b23fc7e 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -253,6 +253,81 @@
 
 export type UserId = AccountId | GroupId | EmailAddress;
 
+// Must be kept in sync with the ListChangesOption enum.
+// See: java/com/google/gerrit/extensions/client/ListChangesOption.java
+export const ListChangesOption = {
+  LABELS: 0,
+  DETAILED_LABELS: 8,
+
+  // Return information on the current patch set of the change.
+  CURRENT_REVISION: 1,
+  ALL_REVISIONS: 2,
+
+  // If revisions are included, parse the commit object.
+  CURRENT_COMMIT: 3,
+  ALL_COMMITS: 4,
+
+  // If a patch set is included, include the files of the patch set.
+  CURRENT_FILES: 5,
+  ALL_FILES: 6,
+
+  // If accounts are included, include detailed account info.
+  DETAILED_ACCOUNTS: 7,
+
+  // Include messages associated with the change.
+  MESSAGES: 9,
+
+  // Include allowed actions client could perform.
+  CURRENT_ACTIONS: 10,
+
+  // Set the reviewed boolean for the caller.
+  REVIEWED: 11,
+
+  // Include download commands for the caller.
+  DOWNLOAD_COMMANDS: 13,
+
+  // Include patch set weblinks.
+  WEB_LINKS: 14,
+
+  // Include consistency check results.
+  CHECK: 15,
+
+  // Include allowed change actions client could perform.
+  CHANGE_ACTIONS: 16,
+
+  // Include a copy of commit messages including review footers.
+  COMMIT_FOOTERS: 17,
+
+  // Include push certificate information along with any patch sets.
+  PUSH_CERTIFICATES: 18,
+
+  // Include change's reviewer updates.
+  REVIEWER_UPDATES: 19,
+
+  // Set the submittable boolean.
+  SUBMITTABLE: 20,
+
+  // If tracking ids are included, include detailed tracking ids info.
+  TRACKING_IDS: 21,
+
+  // Skip mergeability data.
+  SKIP_MERGEABLE: 22,
+
+  // Skip diffstat computation that compute the insertions field (number of lines inserted) and
+  // deletions field (number of lines deleted)
+  SKIP_DIFFSTAT: 23,
+
+  // Include the evaluated submit requirements for the caller.
+  SUBMIT_REQUIREMENTS: 24,
+
+  // Include custom keyed values.
+  CUSTOM_KEYED_VALUES: 25,
+
+  // Include the 'starred' field, that is if the change is starred by the
+  // current user.
+  STAR: 26,
+};
+
 // https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#contributor-agreement-input
 export interface ContributorAgreementInput {
   name?: string;
@@ -1293,6 +1368,7 @@
   add_to_attention_set?: AttentionSetInput[];
   remove_from_attention_set?: AttentionSetInput[];
   ignore_automatic_attention_set_rules?: boolean;
+  response_format_options?: string[];
 }
 
 /**
@@ -1304,6 +1380,7 @@
   labels?: unknown;
   reviewers?: {[key: UserId]: AddReviewerResult};
   ready?: boolean;
+  change_info?: ChangeInfo;
 }
 
 /**
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index 932d91a3..5079abd 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -31,80 +31,6 @@
   REWRITE: 'REWRITE',
 };
 
-// Must be kept in sync with the ListChangesOption enum and protobuf.
-export const ListChangesOption = {
-  LABELS: 0,
-  DETAILED_LABELS: 8,
-
-  // Return information on the current patch set of the change.
-  CURRENT_REVISION: 1,
-  ALL_REVISIONS: 2,
-
-  // If revisions are included, parse the commit object.
-  CURRENT_COMMIT: 3,
-  ALL_COMMITS: 4,
-
-  // If a patch set is included, include the files of the patch set.
-  CURRENT_FILES: 5,
-  ALL_FILES: 6,
-
-  // If accounts are included, include detailed account info.
-  DETAILED_ACCOUNTS: 7,
-
-  // Include messages associated with the change.
-  MESSAGES: 9,
-
-  // Include allowed actions client could perform.
-  CURRENT_ACTIONS: 10,
-
-  // Set the reviewed boolean for the caller.
-  REVIEWED: 11,
-
-  // Include download commands for the caller.
-  DOWNLOAD_COMMANDS: 13,
-
-  // Include patch set weblinks.
-  WEB_LINKS: 14,
-
-  // Include consistency check results.
-  CHECK: 15,
-
-  // Include allowed change actions client could perform.
-  CHANGE_ACTIONS: 16,
-
-  // Include a copy of commit messages including review footers.
-  COMMIT_FOOTERS: 17,
-
-  // Include push certificate information along with any patch sets.
-  PUSH_CERTIFICATES: 18,
-
-  // Include change's reviewer updates.
-  REVIEWER_UPDATES: 19,
-
-  // Set the submittable boolean.
-  SUBMITTABLE: 20,
-
-  // If tracking ids are included, include detailed tracking ids info.
-  TRACKING_IDS: 21,
-
-  // Skip mergeability data.
-  SKIP_MERGEABLE: 22,
-
-  // Skip diffstat computation that compute the insertions field (number of lines inserted) and
-  // deletions field (number of lines deleted)
-  SKIP_DIFFSTAT: 23,
-
-  // Include the evaluated submit requirements for the caller.
-  SUBMIT_REQUIREMENTS: 24,
-
-  // Include custom keyed values.
-  CUSTOM_KEYED_VALUES: 25,
-
-  // Include the 'starred' field, that is if the change is starred by the
-  // current user.
-  STAR: 26,
-};
-
 export function listChangesOptionsToHex(...args: number[]) {
   let v = 0;
   for (let i = 0; i < args.length; i++) {
diff --git a/polygerrit-ui/app/utils/change-util_test.ts b/polygerrit-ui/app/utils/change-util_test.ts
index 1d209f9..6e53c16 100644
--- a/polygerrit-ui/app/utils/change-util_test.ts
+++ b/polygerrit-ui/app/utils/change-util_test.ts
@@ -16,6 +16,7 @@
   AccountId,
   ChangeStates,
   CommitId,
+  ListChangesOption,
   NumericChangeId,
   PatchSetNum,
 } from '../types/common';
@@ -27,7 +28,6 @@
   changePath,
   changeStatuses,
   isRemovableReviewer,
-  ListChangesOption,
   listChangesOptionsToHex,
   hasHumanReviewer,
 } from './change-util';