Merge "Fix documentation about Submit"
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index e8569ec..51421ff 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -51,6 +51,7 @@
   AccountDetailInfo,
   AccountInfo,
   BranchName,
+  ChangeInfo,
   CommitId,
   CommitInfo,
   ElementPropertyDeepChange,
@@ -85,10 +86,7 @@
   AutocompleteQuery,
   AutocompleteSuggestion,
 } from '../../shared/gr-autocomplete/gr-autocomplete';
-import {
-  getRevertCommitHash,
-  isRevertCreated,
-} from '../../../utils/message-util';
+import {getRevertCreatedChangeIds} from '../../../utils/message-util';
 
 const HASHTAG_ADD_MESSAGE = 'Add Hashtag';
 
@@ -142,6 +140,9 @@
   @property({type: Object})
   change?: ParsedChangeInfo;
 
+  @property({type: Object})
+  revertSubmittedChange?: ChangeInfo;
+
   @property({type: Object, notify: true})
   labels?: LabelNameToInfoMap;
 
@@ -591,14 +592,35 @@
     return rev.commit;
   }
 
-  _showRevertCreatedAs(change?: ParsedChangeInfo) {
-    if (!change) return false;
-    return isRevertCreated(change.messages);
+  _getRevertSectionTitle(
+    _change?: ParsedChangeInfo,
+    revertSubmittedChange?: ChangeInfo
+  ) {
+    return revertSubmittedChange ? 'Revert Submitted As' : 'Revert Created As';
   }
 
-  _computeRevertCommit(change?: ParsedChangeInfo) {
-    if (!change) return undefined;
-    return {commit: getRevertCommitHash(change.messages)};
+  _showRevertCreatedAs(change?: ParsedChangeInfo) {
+    if (!change?.messages) return false;
+    return getRevertCreatedChangeIds(change.messages).length > 0;
+  }
+
+  _computeRevertCommit(
+    change?: ParsedChangeInfo,
+    revertSubmittedChange?: ChangeInfo
+  ) {
+    if (
+      revertSubmittedChange?.current_revision &&
+      revertSubmittedChange?.revisions
+    ) {
+      return {
+        commit: this._computeMergedCommitInfo(
+          revertSubmittedChange.current_revision,
+          revertSubmittedChange.revisions
+        ),
+      };
+    }
+    if (!change?.messages) return undefined;
+    return {commit: getRevertCreatedChangeIds(change.messages)?.[0]};
   }
 
   _computeShowAllLabelText(showAllSections: boolean) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
index 9db5533..c7999c9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
@@ -359,11 +359,13 @@
       <section
         class$="[[_computeDisplayState(_showAllSections, change, _SECTION.REVERT_CREATED_AS)]]"
       >
-        <span class="title">Revert Created As</span>
+        <span class="title"
+          >[[_getRevertSectionTitle(change, revertSubmittedChange)]]</span
+        >
         <span class="value">
           <gr-commit-info
             change="[[change]]"
-            commit-info="[[_computeRevertCommit(change)]]"
+            commit-info="[[_computeRevertCommit(change, revertSubmittedChange)]]"
             server-config="[[serverConfig]]"
           ></gr-commit-info>
         </span>
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 34839e6..50d222f 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
@@ -175,6 +175,8 @@
 import {GrRelatedChangesListExperimental} from '../gr-related-changes-list-experimental/gr-related-changes-list-experimental';
 import {debounce, DelayedTask} from '../../../utils/async-util';
 import {Timing} from '../../../constants/reporting';
+import {ChangeStates} from '../../shared/gr-change-status/gr-change-status';
+import {getRevertCreatedChangeIds} from '../../../utils/message-util';
 
 const CHANGE_ID_ERROR = {
   MISMATCH: 'mismatch',
@@ -553,6 +555,9 @@
   @property({type: String})
   _tabState?: TabState;
 
+  @property({type: Object})
+  revertSubmittedChange?: ChangeInfo;
+
   restApiService = appContext.restApiService;
 
   checksService = appContext.checksService;
@@ -1949,6 +1954,26 @@
     }
   }
 
+  computeRevertSubmitted(change?: ChangeInfo | ParsedChangeInfo) {
+    if (!change?.messages) return;
+    Promise.all(
+      getRevertCreatedChangeIds(change.messages).map(changeId =>
+        this.restApiService.getChange(changeId)
+      )
+    ).then(changes => {
+      const change = changes.find(
+        change => change?.status === ChangeStatus.MERGED
+      );
+      if (!this._changeStatuses) return;
+      if (change) {
+        this.revertSubmittedChange = change;
+        this.push('_changeStatuses', ChangeStates.REVERT_SUBMITTED);
+      } else {
+        this.push('_changeStatuses', ChangeStates.REVERT_CREATED);
+      }
+    });
+  }
+
   _getChangeDetail() {
     if (!this._changeNum)
       throw new Error('missing required changeNum property');
@@ -1994,6 +2019,7 @@
         this._lineHeight = Number(lineHeight.slice(0, lineHeight.length - 2));
 
         this._change = change;
+        this.computeRevertSubmitted(change);
         this.changeService.updateChange(change);
         if (
           !this._patchRange ||
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index bb99529..de08318 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -369,6 +369,7 @@
             <template is="dom-repeat" items="[[_changeStatuses]]" as="status">
               <gr-change-status
                 change="[[_change]]"
+                revert-submitted-change="[[revertSubmittedChange]]"
                 max-width="100"
                 status="[[status]]"
               ></gr-change-status>
@@ -430,6 +431,7 @@
           <gr-change-metadata
             id="metadata"
             change="{{_change}}"
+            revert-submitted-change="[[revertSubmittedChange]]"
             account="[[_account]]"
             revision="[[_selectedRevision]]"
             commit-info="[[_commitInfo]]"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index d1f9ff0..45d1b93 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -24,6 +24,7 @@
   DefaultBase,
   DiffViewMode,
   HttpMethod,
+  MessageTag,
   PrimaryTab,
   SecondaryTab,
 } from '../../../constants/constants';
@@ -79,6 +80,7 @@
   ParentPatchSetNum,
   PatchRange,
   PatchSetNum,
+  ReviewInputTag,
   RevisionInfo,
   RevisionPatchSetNum,
   RobotId,
@@ -105,6 +107,7 @@
 import {ParsedChangeInfo} from '../../../types/types';
 import {GrRelatedChangesList} from '../gr-related-changes-list/gr-related-changes-list';
 import {appContext} from '../../../services/app-context';
+import {ChangeStates} from '../../shared/gr-change-status/gr-change-status';
 
 const pluginApi = _testOnly_initGerritPluginApi();
 const fixture = fixtureFromElement('gr-change-view');
@@ -1270,6 +1273,77 @@
     assert.equal(statusChips.length, 2);
   });
 
+  suite('ChangeStatus revert', () => {
+    test('show revert created if no revert is merged', done => {
+      const change = {
+        ...createChange(),
+        messages: createChangeMessages(2),
+      };
+      change.messages[0].message = 'Created a revert of this change as 12345';
+      change.messages[0].tag = MessageTag.TAG_REVERT as ReviewInputTag;
+      const getChangeStub = stubRestApi('getChange');
+      getChangeStub.onFirstCall().returns(
+        Promise.resolve({
+          ...createChange(),
+        })
+      );
+      getChangeStub.onSecondCall().returns(
+        Promise.resolve({
+          ...createChange(),
+        })
+      );
+      element._change = change;
+      element._mergeable = true;
+      element._submitEnabled = true;
+      flush();
+      element.computeRevertSubmitted(element._change);
+      flush(() => {
+        assert.isFalse(
+          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+        );
+        assert.isTrue(
+          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+        );
+        done();
+      });
+    });
+
+    test('show revert created if no revert is merged', done => {
+      const change = {
+        ...createChange(),
+        messages: createChangeMessages(2),
+      };
+      change.messages[0].message = 'Created a revert of this change as 12345';
+      change.messages[0].tag = MessageTag.TAG_REVERT as ReviewInputTag;
+      const getChangeStub = stubRestApi('getChange');
+      getChangeStub.onFirstCall().returns(
+        Promise.resolve({
+          ...createChange(),
+          status: ChangeStatus.MERGED,
+        })
+      );
+      getChangeStub.onSecondCall().returns(
+        Promise.resolve({
+          ...createChange(),
+        })
+      );
+      element._change = change;
+      element._mergeable = true;
+      element._submitEnabled = true;
+      flush();
+      element.computeRevertSubmitted(element._change);
+      flush(() => {
+        assert.isFalse(
+          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+        );
+        assert.isTrue(
+          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+        );
+        done();
+      });
+    });
+  });
+
   test('diff preferences open when open-diff-prefs is fired', () => {
     const overlayOpenStub = sinon.stub(element.$.fileList, 'openDiffPrefs');
     element.$.fileListHeader.dispatchEvent(
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
index f18f121..89cab8a 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
@@ -20,17 +20,18 @@
 import {htmlTemplate} from './gr-change-status_html';
 import {customElement, property} from '@polymer/decorators';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {getRevertCommitHash} from '../../../utils/message-util';
+import {getRevertCreatedChangeIds} from '../../../utils/message-util';
 import {ChangeInfo} from '../../../types/common';
 import {ParsedChangeInfo} from '../../../types/types';
 
-enum ChangeStates {
+export enum ChangeStates {
   MERGED = 'Merged',
   ABANDONED = 'Abandoned',
   MERGE_CONFLICT = 'Merge Conflict',
   WIP = 'WIP',
   PRIVATE = 'Private',
   REVERT_CREATED = 'Revert Created',
+  REVERT_SUBMITTED = 'Revert Submitted',
 }
 
 const WIP_TOOLTIP =
@@ -65,6 +66,9 @@
   @property({type: String})
   tooltipText = '';
 
+  @property({type: Object})
+  revertSubmittedChange?: ChangeInfo;
+
   _computeStatusString(status: ChangeStates) {
     if (status === ChangeStates.WIP && !this.flat) {
       return 'Work in Progress';
@@ -77,14 +81,25 @@
   }
 
   hasStatusLink(status: ChangeStates) {
-    return status === ChangeStates.REVERT_CREATED;
+    return (
+      status === ChangeStates.REVERT_CREATED ||
+      status === ChangeStates.REVERT_SUBMITTED
+    );
   }
 
-  getStatusLink(change?: ParsedChangeInfo) {
-    if (!change) return;
-    const revertCommit = getRevertCommitHash(change.messages);
-    if (!revertCommit) return;
-    return GerritNav.getUrlForSearchQuery(revertCommit);
+  getStatusLink(change?: ParsedChangeInfo, status?: ChangeStates) {
+    if (!change?.messages) return;
+    if (status === ChangeStates.REVERT_CREATED) {
+      const revertChangeId = getRevertCreatedChangeIds(change.messages)?.[0];
+      if (!revertChangeId) return;
+      return GerritNav.getUrlForSearchQuery(revertChangeId);
+    }
+    if (this.revertSubmittedChange) {
+      return GerritNav.getUrlForSearchQuery(
+        `${this.revertSubmittedChange._number}`
+      );
+    }
+    return;
   }
 
   _updateChipDetails(status?: ChangeStates, previousStatus?: ChangeStates) {
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
index 379c3f3..2a96cdf 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
@@ -56,6 +56,10 @@
       background-color: var(--status-revert-created);
       color: var(--status-revert-created);
     }
+    :host(.revert-submitted) .chip {
+      background-color: var(--status-revert-created);
+      color: var(--status-revert-created);
+    }
     .status-link {
       text-decoration: none;
     }
@@ -78,7 +82,7 @@
     max-width="40em"
   >
     <template is="dom-if" if="[[hasStatusLink(status)]]">
-      <a class="status-link" href="[[getStatusLink(change)]]">
+      <a class="status-link" href="[[getStatusLink(change, status)]]">
         <div class="chip" aria-label$="Label: [[status]]">
           [[_computeStatusString(status)]]
         </div>
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 30bf490..2d5242e 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
@@ -206,7 +206,7 @@
 
   getChange(
     changeNum: ChangeId | NumericChangeId,
-    errFn: ErrorCallback
+    errFn?: ErrorCallback
   ): Promise<ChangeInfo | null>;
 
   savePreferences(prefs: PreferencesInput): Promise<Response>;
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index b2d23a0..dd91f7b 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -24,7 +24,6 @@
   RelatedChangeAndCommitInfo,
 } from '../types/common';
 import {ParsedChangeInfo} from '../types/types';
-import {isRevertCreated} from './message-util';
 
 // This can be wrong! See WARNING above
 interface ChangeStatusesOptions {
@@ -158,9 +157,6 @@
   if (change.is_private) {
     states.push('Private');
   }
-  if (isRevertCreated(change.messages)) {
-    states.push('Revert Created');
-  }
 
   // If there are any pre-defined statuses, only return those. Otherwise,
   // will determine the derived status.
diff --git a/polygerrit-ui/app/utils/message-util.ts b/polygerrit-ui/app/utils/message-util.ts
index f2d7e6b..70dd286 100644
--- a/polygerrit-ui/app/utils/message-util.ts
+++ b/polygerrit-ui/app/utils/message-util.ts
@@ -16,17 +16,17 @@
  */
 
 import {MessageTag} from '../constants/constants';
-import {ChangeMessageInfo} from '../types/common';
+import {ChangeId, ChangeMessageInfo} from '../types/common';
 
-export function getRevertCommitHash(messages?: ChangeMessageInfo[]) {
-  const msg = messages?.find(m => m.tag === MessageTag.TAG_REVERT);
-  if (!msg) return undefined;
+function getRevertChangeIdFromMessage(msg: ChangeMessageInfo): ChangeId {
   const REVERT_REGEX = /^Created a revert of this change as (.*)$/;
-  const commit = msg.message.match(REVERT_REGEX)?.[1];
-  if (!commit) throw new Error('revert commit not found');
-  return commit;
+  const changeId = msg.message.match(REVERT_REGEX)?.[1];
+  if (!changeId) throw new Error('revert changeId not found');
+  return changeId as ChangeId;
 }
 
-export function isRevertCreated(messages?: ChangeMessageInfo[]) {
-  return messages?.some(m => m.tag === MessageTag.TAG_REVERT);
+export function getRevertCreatedChangeIds(messages: ChangeMessageInfo[]) {
+  return messages
+    .filter(m => m.tag === MessageTag.TAG_REVERT)
+    .map(m => getRevertChangeIdFromMessage(m));
 }
diff --git a/polygerrit-ui/app/utils/patch-set-util.ts b/polygerrit-ui/app/utils/patch-set-util.ts
index 138479c..5034028 100644
--- a/polygerrit-ui/app/utils/patch-set-util.ts
+++ b/polygerrit-ui/app/utils/patch-set-util.ts
@@ -309,6 +309,7 @@
  *     has been loaded, and false if a newer patch has been uploaded in the
  *     meantime. The promise is rejected on network error.
  */
+// TODO: remove usage of RestApiService inside util
 export function fetchChangeUpdates(
   change: ChangeInfo | ParsedChangeInfo,
   restAPI: RestApiService