Show View Diff button for merge change messages

Upon submitting a change with unreviewed files(when approval is sticky),
Gerrit sends a change message containing the latest approved patchset.
Add a View Diff button to this change message which shows the diff
between this approved patchset and the latest patchset.

Change-Id: I8a86c8ebe54726100aa8ca97c2c11e29673a1df3
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts
index b0f87d7..03d5000 100644
--- a/polygerrit-ui/app/constants/constants.ts
+++ b/polygerrit-ui/app/constants/constants.ts
@@ -53,6 +53,7 @@
   TAG_SET_WIP = 'autogenerated:gerrit:setWorkInProgress',
   TAG_SET_ASSIGNEE = 'autogenerated:gerrit:setAssignee',
   TAG_UNSET_ASSIGNEE = 'autogenerated:gerrit:deleteAssignee',
+  TAG_MERGED = 'autogenerated:gerrit:merged',
 }
 
 /**
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index bc61dad..39cdf22 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -27,7 +27,7 @@
 import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-message_html';
-import {SpecialFilePath} from '../../../constants/constants';
+import {MessageTag, SpecialFilePath} from '../../../constants/constants';
 import {customElement, property, computed, observe} from '@polymer/decorators';
 import {
   ChangeInfo,
@@ -54,6 +54,8 @@
 
 const PATCH_SET_PREFIX_PATTERN = /^(?:Uploaded\s*)?(?:P|p)atch (?:S|s)et \d+:\s*(.*)/;
 const LABEL_TITLE_SCORE_PATTERN = /^(-?)([A-Za-z0-9-]+?)([+-]\d+)?[.]?$/;
+const UPLOADED_NEW_PATCHSET_PATTERN = /Uploaded patch set (\d+)./;
+const MERGED_PATCHSET_PATTERN = /(\d+) is the latest approved patch-set/;
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -268,28 +270,49 @@
     return this._patchsetCommentSummary(commentThreads);
   }
 
+  _showViewDiffButton(message?: ChangeMessage) {
+    return (
+      this._isNewPatchsetTag(message?.tag) || this._isMergePatchset(message)
+    );
+  }
+
+  _isMergePatchset(message?: ChangeMessage) {
+    return (
+      message?.tag === MessageTag.TAG_MERGED &&
+      message?.message.match(MERGED_PATCHSET_PATTERN)
+    );
+  }
+
   _isNewPatchsetTag(tag?: ReviewInputTag) {
-    return tag?.endsWith(':newPatchSet') || tag?.endsWith(':newWipPatchSet');
+    return (
+      tag === MessageTag.TAG_NEW_PATCHSET ||
+      tag === MessageTag.TAG_NEW_WIP_PATCHSET
+    );
   }
 
   _handleViewPatchsetDiff(e: Event) {
     if (!this.message || !this.change) return;
-    const match = this.message.message.match(/Uploaded patch set (\d+)./);
     let patchNum: PatchSetNum;
-    // Message is of the form "Commit Message was updated" or "Patchset X
-    // was rebased"
-    if (!match || match.length < 1) {
-      patchNum = computeLatestPatchNum(computeAllPatchSets(this.change))!;
-    } else {
+    let basePatchNum: PatchSetNum;
+    if (this.message.message.match(UPLOADED_NEW_PATCHSET_PATTERN)) {
+      const match = this.message.message.match(UPLOADED_NEW_PATCHSET_PATTERN)!;
       if (isNaN(Number(match[1])))
         throw new Error('invalid patchnum in message');
       patchNum = Number(match[1]) as PatchSetNum;
+      basePatchNum = computePredecessor(patchNum)!;
+    } else if (this.message.message.match(MERGED_PATCHSET_PATTERN)) {
+      const match = this.message.message.match(MERGED_PATCHSET_PATTERN)!;
+      if (isNaN(Number(match[1])))
+        throw new Error('invalid patchnum in message');
+      basePatchNum = Number(match[1]) as PatchSetNum;
+      patchNum = computeLatestPatchNum(computeAllPatchSets(this.change))!;
+    } else {
+      // Message is of the form "Commit Message was updated" or "Patchset X
+      // was rebased"
+      patchNum = computeLatestPatchNum(computeAllPatchSets(this.change))!;
+      basePatchNum = computePredecessor(patchNum)!;
     }
-    GerritNav.navigateToChange(
-      this.change,
-      patchNum,
-      computePredecessor(patchNum)
-    );
+    GerritNav.navigateToChange(this.change, patchNum, basePatchNum);
     // stop propagation to stop message expansion
     e.stopPropagation();
   }
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
index 48e5e24..070aa2d 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
@@ -279,7 +279,7 @@
         </div>
       </template>
       <span class="dateContainer">
-        <template is="dom-if" if="[[_isNewPatchsetTag(message.tag)]]">
+        <template is="dom-if" if="[[_showViewDiffButton(message)]]">
           <gr-button
             class="patchsetDiffButton"
             on-click="_handleViewPatchsetDiff"
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js
index e2c7e86..3f4e13a 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js
@@ -266,6 +266,14 @@
         element._handleViewPatchsetDiff(new MouseEvent('click'));
         assert.isTrue(navStub.calledWithExactly(element.change, 4, 3));
       });
+
+      test('Merged patchset change message', () => {
+        element.message = {
+          message: 'abcd↵3 is the latest approved patch-set.↵abc',
+        };
+        element._handleViewPatchsetDiff(new MouseEvent('click'));
+        assert.isTrue(navStub.calledWithExactly(element.change, 4, 3));
+      });
     });
 
     suite('compute messages', () => {