Merge "Refactor show-alert events to use event-util"
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
index ae10c03..e94a933 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
@@ -46,6 +46,7 @@
 import {AutocompleteQuery} from '../../shared/gr-autocomplete/gr-autocomplete';
 import {PolymerDomRepeatEvent} from '../../../types/types';
 import {hasOwnProperty} from '../../../utils/common-util';
+import {EventType, fire} from '../../../utils/event-util';
 
 const SUGGESTIONS_LIMIT = 15;
 const SAVING_ERROR_TEXT =
@@ -297,13 +298,7 @@
         (errResponse, err) => {
           if (errResponse) {
             if (errResponse.status === 404) {
-              this.dispatchEvent(
-                new CustomEvent('show-alert', {
-                  detail: {message: SAVING_ERROR_TEXT},
-                  bubbles: true,
-                  composed: true,
-                })
-              );
+              fire(this, EventType.SHOW_ALERT, SAVING_ERROR_TEXT);
               return errResponse;
             }
             throw Error(errResponse.statusText);
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
index b96ec2c..ac65297 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -52,6 +52,7 @@
   PropertyTreeNode,
   PrimitiveValue,
 } from './gr-repo-access-interfaces';
+import {EventType, fire} from '../../../utils/event-util';
 
 const NOTHING_TO_SAVE = 'No changes to save.';
 
@@ -516,13 +517,7 @@
       !Object.keys(addRemoveObj.remove).length &&
       !addRemoveObj.parent
     ) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: NOTHING_TO_SAVE},
-          bubbles: true,
-          composed: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, NOTHING_TO_SAVE);
       return;
     }
     const obj: ProjectAccessInput = ({
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
index a74f4bb..7fce91f 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
@@ -42,6 +42,7 @@
 } from '../../../types/common';
 import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
 import {GrCreateChangeDialog} from '../gr-create-change-dialog/gr-create-change-dialog';
+import {fire, EventType} from '../../../utils/event-util';
 
 const GC_MESSAGE = 'Garbage collection completed successfully.';
 const CONFIG_BRANCH = 'refs/meta/config' as BranchName;
@@ -142,13 +143,7 @@
       .runRepoGC(this.repo)
       .then(response => {
         if (response?.status === 200) {
-          this.dispatchEvent(
-            new CustomEvent('show-alert', {
-              detail: {message: GC_MESSAGE},
-              bubbles: true,
-              composed: true,
-            })
-          );
+          fire(this, EventType.SHOW_ALERT, GC_MESSAGE);
         }
       })
       .finally(() => {
@@ -190,13 +185,7 @@
         const message = change
           ? CREATE_CHANGE_SUCCEEDED_MESSAGE
           : CREATE_CHANGE_FAILED_MESSAGE;
-        this.dispatchEvent(
-          new CustomEvent('show-alert', {
-            detail: {message},
-            bubbles: true,
-            composed: true,
-          })
-        );
+        fire(this, EventType.SHOW_ALERT, message);
         if (!change) {
           return;
         }
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 3f6dd23..1eff047 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
@@ -54,7 +54,10 @@
   HttpMethod,
   NotifyType,
 } from '../../../constants/constants';
-import {EventType, TargetElement} from '../../plugins/gr-plugin-types';
+import {
+  EventType as PluginEventType,
+  TargetElement,
+} from '../../plugins/gr-plugin-types';
 import {customElement, observe, property} from '@polymer/decorators';
 import {GrJsApiInterface} from '../../shared/gr-js-api-interface/gr-js-api-interface-element';
 import {
@@ -110,6 +113,7 @@
   RevisionActions,
   UIActionInfo,
 } from '../../shared/gr-js-api-interface/gr-change-actions-js-api';
+import {fire, EventType} from '../../../utils/event-util';
 
 const ERR_BRANCH_EMPTY = 'The destination branch can’t be empty.';
 const ERR_COMMIT_EMPTY = 'The commit message can’t be empty.';
@@ -609,13 +613,7 @@
         this._handleLoadingComplete();
       })
       .catch(err => {
-        this.dispatchEvent(
-          new CustomEvent('show-alert', {
-            detail: {message: ERR_REVISION_ACTIONS},
-            composed: true,
-            bubbles: true,
-          })
-        );
+        fire(this, EventType.SHOW_ALERT, ERR_REVISION_ACTIONS);
         this._loading = false;
         throw err;
       });
@@ -631,7 +629,7 @@
     change: ChangeInfo;
     revisionActions: ActionNameToActionInfoMap;
   }) {
-    this.$.jsAPI.handleEvent(EventType.SHOW_REVISION_ACTIONS, detail);
+    this.$.jsAPI.handleEvent(PluginEventType.SHOW_REVISION_ACTIONS, detail);
   }
 
   @observe('change')
@@ -1370,23 +1368,11 @@
   _handleCherryPickRestApi(conflicts: boolean) {
     const el = this.$.confirmCherrypick;
     if (!el.branch) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: ERR_BRANCH_EMPTY},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, ERR_BRANCH_EMPTY);
       return;
     }
     if (!el.message) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: ERR_COMMIT_EMPTY},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, ERR_COMMIT_EMPTY);
       return;
     }
     this.$.overlay.close();
@@ -1407,13 +1393,7 @@
   _handleMoveConfirm() {
     const el = this.$.confirmMove;
     if (!el.branch) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: ERR_BRANCH_EMPTY},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, ERR_BRANCH_EMPTY);
       return;
     }
     this.$.overlay.close();
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 2c9b31c..f3ea363 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
@@ -73,7 +73,7 @@
   PatchSet,
 } from '../../../utils/patch-set-util';
 import {changeStatuses, changeStatusString} from '../../../utils/change-util';
-import {EventType} from '../../plugins/gr-plugin-types';
+import {EventType as PluginEventType} from '../../plugins/gr-plugin-types';
 import {customElement, property, observe} from '@polymer/decorators';
 import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
 import {GrJsApiInterface} from '../../shared/gr-js-api-interface/gr-js-api-interface-element';
@@ -152,6 +152,7 @@
 import {GrMessagesList} from '../gr-messages-list/gr-messages-list';
 import {GrThreadList} from '../gr-thread-list/gr-thread-list';
 import {PORTING_COMMENTS_CHANGE_LATENCY_LABEL} from '../../../services/gr-reporting/gr-reporting';
+import {fire, EventType} from '../../../utils/event-util';
 
 const CHANGE_ID_ERROR = {
   MISMATCH: 'mismatch',
@@ -1297,7 +1298,7 @@
   _sendShowChangeEvent() {
     if (!this._patchRange)
       throw new Error('missing required _patchRange property');
-    this.$.jsAPI.handleEvent(EventType.SHOW_CHANGE, {
+    this.$.jsAPI.handleEvent(PluginEventType.SHOW_CHANGE, {
       change: this._change,
       patchNum: this._patchRange.patchNum,
       info: {mergeable: this._mergeable},
@@ -1654,15 +1655,7 @@
     if (!this._patchRange)
       throw new Error('missing required _patchRange property');
     if (patchNumEquals(this._patchRange.basePatchNum, ParentPatchSetNum)) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Base is already selected.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Base is already selected.');
       return;
     }
     GerritNav.navigateToChange(this._change, this._patchRange.patchNum);
@@ -1676,15 +1669,7 @@
     if (!this._patchRange)
       throw new Error('missing required _patchRange property');
     if (patchNumEquals(this._patchRange.basePatchNum, ParentPatchSetNum)) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Left is already base.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Left is already base.');
       return;
     }
     GerritNav.navigateToChange(this._change, this._patchRange.basePatchNum);
@@ -1727,15 +1712,7 @@
     if (!this._patchRange)
       throw new Error('missing required _patchRange property');
     if (patchNumEquals(this._patchRange.patchNum, latestPatchNum)) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Right is already latest.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Right is already latest.');
       return;
     }
     GerritNav.navigateToChange(
@@ -1757,15 +1734,7 @@
       patchNumEquals(this._patchRange.patchNum, latestPatchNum) &&
       patchNumEquals(this._patchRange.basePatchNum, ParentPatchSetNum)
     ) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Already diffing base against latest.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Already diffing base against latest.');
       return;
     }
     GerritNav.navigateToChange(this._change, latestPatchNum);
@@ -1873,7 +1842,7 @@
         changeRecord.path
       );
     }
-    this.$.jsAPI.handleEvent(EventType.LABEL_CHANGE, {
+    this.$.jsAPI.handleEvent(PluginEventType.LABEL_CHANGE, {
       change: this._change,
     });
   }
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
index 5c0b19f..b3e054f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
@@ -25,6 +25,7 @@
 import {customElement, property} from '@polymer/decorators';
 import {JsApiService} from '../../shared/gr-js-api-interface/gr-js-api-types';
 import {ChangeInfo, CommitId} from '../../../types/common';
+import {fire, EventType} from '../../../utils/event-util';
 
 const ERR_COMMIT_NOT_FOUND = 'Unable to find the commit hash of this change.';
 const CHANGE_SUBJECT_LIMIT = 50;
@@ -124,13 +125,7 @@
     const originalTitle = (commitMessage || '').split('\n')[0];
     const revertTitle = `Revert "${originalTitle}"`;
     if (!commitHash) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: ERR_COMMIT_NOT_FOUND},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, ERR_COMMIT_NOT_FOUND);
       return;
     }
     const revertCommitText = `This reverts commit ${commitHash}.`;
@@ -168,13 +163,7 @@
     // Follow the same convention of the revert
     const commitHash = change.current_revision;
     if (!commitHash) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: ERR_COMMIT_NOT_FOUND},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, ERR_COMMIT_NOT_FOUND);
       return;
     }
     if (!changes || changes.length <= 1) return;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.ts
index 9754e89..2f9f800 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.ts
@@ -24,6 +24,7 @@
 import {customElement, property} from '@polymer/decorators';
 import {JsApiService} from '../../shared/gr-js-api-interface/gr-js-api-types';
 import {ChangeInfo} from '../../../types/common';
+import {fire, EventType} from '../../../utils/event-util';
 
 const ERR_COMMIT_NOT_FOUND = 'Unable to find the commit hash of this change.';
 const CHANGE_SUBJECT_LIMIT = 50;
@@ -86,13 +87,7 @@
     // Follow the same convention of the revert
     const commitHash = change.current_revision;
     if (!commitHash) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: ERR_COMMIT_NOT_FOUND},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, ERR_COMMIT_NOT_FOUND);
       return;
     }
     const revertTitle = `Revert submission ${change.submission_id}`;
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 7b34263..12f8021 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
@@ -110,6 +110,7 @@
 import {isAttentionSetEnabled} from '../../../utils/attention-set-util';
 import {CODE_REVIEW, getMaxAccounts} from '../../../utils/label-util';
 import {isUnresolved} from '../../../utils/comment-util';
+import {fire, EventType} from '../../../utils/event-util';
 
 const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
 
@@ -548,13 +549,7 @@
             const moveTo = isReviewer ? 'reviewer' : 'CC';
             const id = account.name || account.email || key;
             const message = `${id} moved from ${moveFrom} to ${moveTo}.`;
-            this.dispatchEvent(
-              new CustomEvent('show-alert', {
-                detail: {message},
-                composed: true,
-                bubbles: true,
-              })
-            );
+            fire(this, EventType.SHOW_ALERT, message);
           }
         }
       }
@@ -1275,13 +1270,7 @@
       return;
     }
     if (this._sendDisabled) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          bubbles: true,
-          composed: true,
-          detail: {message: EMPTY_REPLY_MESSAGE},
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, EMPTY_REPLY_MESSAGE);
       return;
     }
     return this.send(this._includeComments, this.canBeStarted)
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
index 9e64ee6..b48efb4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -48,6 +48,7 @@
 import {GrDiffGroup} from '../gr-diff/gr-diff-group';
 import {PolymerSpliceChange} from '@polymer/polymer/interfaces';
 import {getLineNumber} from '../gr-diff/gr-diff-utils';
+import {fire, EventType} from '../../../utils/event-util';
 
 const DiffViewMode = {
   SIDE_BY_SIDE: 'SIDE_BY_SIDE',
@@ -359,15 +360,7 @@
     const message =
       `The value of the '${pref}' user preference is ` +
       'invalid. Fix in diff preferences';
-    this.dispatchEvent(
-      new CustomEvent('show-alert', {
-        detail: {
-          message,
-        },
-        bubbles: true,
-        composed: true,
-      })
-    );
+    fire(this, EventType.SHOW_ALERT, message);
     throw Error(`Invalid preference value: ${pref}`);
   }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
index 03c09ab..5090462 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
@@ -36,6 +36,7 @@
 import {PolymerDomWrapper} from '../../../types/types';
 import {GrDiffGroupType} from '../gr-diff/gr-diff-group';
 import {GrDiff} from '../gr-diff/gr-diff';
+import {fire, EventType} from '../../../utils/event-util';
 
 const DiffViewMode = {
   SIDE_BY_SIDE: 'SIDE_BY_SIDE',
@@ -227,14 +228,10 @@
         );
       }
       this.lastDisplayedNavigateToNextFileToast = Date.now();
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Press n again to navigate to next unreviewed file',
-          },
-          composed: true,
-          bubbles: true,
-        })
+      fire(
+        this,
+        EventType.SHOW_ALERT,
+        'Press n again to navigate to next unreviewed file'
       );
     }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 7c365d2..9d24b3e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -101,6 +101,7 @@
 import {AppElementParams} from '../../gr-app-types';
 import {CustomKeyboardEvent, OpenFixPreviewEvent} from '../../../types/events';
 import {PORTING_COMMENTS_DIFF_LATENCY_LABEL} from '../../../services/gr-reporting/gr-reporting';
+import {fire, EventType} from '../../../utils/event-util';
 
 const ERR_REVIEW_STATUS = 'Couldn’t change file review status.';
 const MSG_LOADING_BLAME = 'Loading blame...';
@@ -457,13 +458,7 @@
     this.$.reviewed.checked = reviewed;
     if (!this._patchRange?.patchNum) return;
     this._saveReviewedState(reviewed).catch(err => {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: ERR_REVIEW_STATUS},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, ERR_REVIEW_STATUS);
       throw err;
     });
   }
@@ -881,18 +876,12 @@
 
   _displayDiffBaseAgainstLeftToast() {
     if (!this._patchRange) return;
-    this.dispatchEvent(
-      new CustomEvent('show-alert', {
-        detail: {
-          // \u2190 = ←
-          message:
-            `Patchset ${this._patchRange.basePatchNum} vs ` +
-            `${this._patchRange.patchNum} selected. Press v + \u2190 to view ` +
-            `Base vs ${this._patchRange.basePatchNum}`,
-        },
-        composed: true,
-        bubbles: true,
-      })
+    fire(
+      this,
+      EventType.SHOW_ALERT,
+      `Patchset ${this._patchRange.basePatchNum} vs ` +
+        `${this._patchRange.patchNum} selected. Press v + \u2190 to view ` +
+        `Base vs ${this._patchRange.basePatchNum}`
     );
   }
 
@@ -904,17 +893,12 @@
     )
       ? 'Base'
       : `Patchset ${this._patchRange.basePatchNum}`;
-    this.dispatchEvent(
-      new CustomEvent('show-alert', {
-        detail: {
-          // \u2191 = ↑
-          message: `${leftPatchset} vs
+    fire(
+      this,
+      EventType.SHOW_ALERT,
+      `${leftPatchset} vs
             ${this._patchRange.patchNum} selected\n. Press v + \u2191 to view
-            ${leftPatchset} vs Patchset ${latestPatchNum}`,
-        },
-        composed: true,
-        bubbles: true,
-      })
+            ${leftPatchset} vs Patchset ${latestPatchNum}`
     );
   }
 
@@ -966,15 +950,7 @@
         this.params.commentId
       );
       if (!comment) {
-        this.dispatchEvent(
-          new CustomEvent('show-alert', {
-            detail: {
-              message: 'comment not found',
-            },
-            composed: true,
-            bubbles: true,
-          })
-        );
+        fire(this, EventType.SHOW_ALERT, 'comment not found');
         GerritNav.navigateToChange(this._change);
         return;
       }
@@ -1123,17 +1099,13 @@
             return;
           }
 
-          this.dispatchEvent(
-            new CustomEvent('show-alert', {
-              detail: {
-                message: `File is unchanged between Patchset
+          fire(
+            this,
+            EventType.SHOW_ALERT,
+            `File is unchanged between Patchset
                   ${this._patchRange.basePatchNum} and
                   ${this._patchRange.patchNum}. Showing diff of Base vs
-                  ${this._patchRange.basePatchNum}`,
-              },
-              composed: true,
-              bubbles: true,
-            })
+                  ${this._patchRange.basePatchNum}`
           );
           GerritNav.navigateToDiff(
             this._change,
@@ -1691,24 +1663,12 @@
 
   _loadBlame() {
     this._isBlameLoading = true;
-    this.dispatchEvent(
-      new CustomEvent('show-alert', {
-        detail: {message: MSG_LOADING_BLAME},
-        composed: true,
-        bubbles: true,
-      })
-    );
+    fire(this, EventType.SHOW_ALERT, MSG_LOADING_BLAME);
     this.$.diffHost
       .loadBlame()
       .then(() => {
         this._isBlameLoading = false;
-        this.dispatchEvent(
-          new CustomEvent('show-alert', {
-            detail: {message: MSG_LOADED_BLAME},
-            composed: true,
-            bubbles: true,
-          })
-        );
+        fire(this, EventType.SHOW_ALERT, MSG_LOADED_BLAME);
       })
       .catch(() => {
         this._isBlameLoading = false;
@@ -1754,15 +1714,7 @@
     if (!this._patchRange) return;
 
     if (patchNumEquals(this._patchRange.basePatchNum, ParentPatchSetNum)) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Base is already selected.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Base is already selected.');
       return;
     }
     GerritNav.navigateToDiff(
@@ -1779,15 +1731,7 @@
     if (!this._patchRange) return;
 
     if (patchNumEquals(this._patchRange.basePatchNum, ParentPatchSetNum)) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Left is already base.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Left is already base.');
       return;
     }
     GerritNav.navigateToDiff(
@@ -1809,15 +1753,7 @@
 
     const latestPatchNum = computeLatestPatchNum(this._allPatchSets);
     if (patchNumEquals(this._patchRange.patchNum, latestPatchNum)) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Latest is already selected.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Latest is already selected.');
       return;
     }
 
@@ -1837,15 +1773,7 @@
 
     const latestPatchNum = computeLatestPatchNum(this._allPatchSets);
     if (patchNumEquals(this._patchRange.patchNum, latestPatchNum)) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Right is already latest.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Right is already latest.');
       return;
     }
     GerritNav.navigateToDiff(
@@ -1867,15 +1795,7 @@
       patchNumEquals(this._patchRange.patchNum, latestPatchNum) &&
       patchNumEquals(this._patchRange.basePatchNum, ParentPatchSetNum)
     ) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'Already diffing base against latest.',
-          },
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Already diffing base against latest.');
       return;
     }
     GerritNav.navigateToDiff(this._change, this._path, latestPatchNum);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index b4d520b..d036f27 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -57,6 +57,7 @@
 import {FlattenedNodesObserver} from '@polymer/polymer/lib/utils/flattened-nodes-observer';
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 import {AbortStop} from '../../shared/gr-cursor-manager/gr-cursor-manager';
+import {fire, EventType} from '../../../utils/event-util';
 
 const NO_NEWLINE_BASE = 'No newline at end of base file.';
 const NO_NEWLINE_REVISION = 'No newline at end of revision file.';
@@ -570,13 +571,7 @@
 
     const lineNum = getLineNumber(el);
     if (lineNum === null) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: 'Invalid line number'},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'Invalid line number');
       return;
     }
 
@@ -618,12 +613,10 @@
       return false;
     }
     if (!this.patchRange) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: 'Cannot create comment. Patch range undefined.'},
-          composed: true,
-          bubbles: true,
-        })
+      fire(
+        this,
+        EventType.SHOW_ALERT,
+        'Cannot create comment. Patch range undefined.'
       );
       return false;
     }
@@ -637,24 +630,14 @@
       patchNumEquals(this.patchRange.patchNum, EditPatchSetNum);
 
     if (isEdit) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {message: 'You cannot comment on an edit.'},
-          composed: true,
-          bubbles: true,
-        })
-      );
+      fire(this, EventType.SHOW_ALERT, 'You cannot comment on an edit.');
       return false;
     }
     if (isEditBase) {
-      this.dispatchEvent(
-        new CustomEvent('show-alert', {
-          detail: {
-            message: 'You cannot comment on the base patchset of an edit.',
-          },
-          composed: true,
-          bubbles: true,
-        })
+      fire(
+        this,
+        EventType.SHOW_ALERT,
+        'You cannot comment on the base patchset of an edit.'
       );
       return false;
     }
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index a0562de..8121855 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -47,6 +47,7 @@
 } from '../../../types/common';
 import {GrStorage} from '../../shared/gr-storage/gr-storage';
 import {HttpMethod, NotifyType} from '../../../constants/constants';
+import {fire, EventType} from '../../../utils/event-util';
 
 const RESTORED_MESSAGE = 'Content restored from a previous edit.';
 const SAVING_MESSAGE = 'Saving changes...';
@@ -255,13 +256,7 @@
           storedContent.message &&
           storedContent.message !== content
         ) {
-          this.dispatchEvent(
-            new CustomEvent('show-alert', {
-              detail: {message: RESTORED_MESSAGE},
-              bubbles: true,
-              composed: true,
-            })
-          );
+          fire(this, EventType.SHOW_ALERT, RESTORED_MESSAGE);
 
           this._newContent = storedContent.message;
         } else {
@@ -305,13 +300,7 @@
   }
 
   _showAlert(message: string) {
-    this.dispatchEvent(
-      new CustomEvent('show-alert', {
-        detail: {message},
-        bubbles: true,
-        composed: true,
-      })
-    );
+    fire(this, EventType.SHOW_ALERT, message);
   }
 
   _computeSaveDisabled(
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
index 28cd672..6a906fa 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
@@ -32,6 +32,7 @@
   GroupInfo,
   ContributorAgreementInfo,
 } from '../../../types/common';
+import {fire, EventType} from '../../../utils/event-util';
 
 export interface GrClaView {
   $: {
@@ -156,13 +157,7 @@
   }
 
   _createToast(message: string) {
-    this.dispatchEvent(
-      new CustomEvent('show-alert', {
-        detail: {message},
-        bubbles: true,
-        composed: true,
-      })
-    );
+    fire(this, EventType.SHOW_ALERT, message);
   }
 
   _computeShowAgreementsClass(showAgreements: boolean) {
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 9f9840b..6fcb6e8 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -68,6 +68,7 @@
 import {GerritView} from '../../core/gr-navigation/gr-navigation';
 import {GrEmailEditor} from '../gr-email-editor/gr-email-editor';
 import {CustomKeyboardEvent} from '../../../types/events';
+import {fire, EventType} from '../../../utils/event-util';
 
 const PREFS_SECTION_FIELDS: Array<keyof PreferencesInput> = [
   'changes_per_page',
@@ -286,13 +287,7 @@
       promises.push(
         this.$.restAPI.confirmEmail(this.params.emailToken).then(message => {
           if (message) {
-            this.dispatchEvent(
-              new CustomEvent('show-alert', {
-                detail: {message},
-                composed: true,
-                bubbles: true,
-              })
-            );
+            fire(this, EventType.SHOW_ALERT, message);
           }
           this.$.emailEditor.loadData();
         })
@@ -542,14 +537,10 @@
       applyDarkTheme();
     }
     this._isDark = !!window.localStorage.getItem('dark-theme');
-    this.dispatchEvent(
-      new CustomEvent('show-alert', {
-        detail: {
-          message: `Theme changed to ${this._isDark ? 'dark' : 'light'}.`,
-        },
-        bubbles: true,
-        composed: true,
-      })
+    fire(
+      this,
+      EventType.SHOW_ALERT,
+      `Theme changed to ${this._isDark ? 'dark' : 'light'}.`
     );
   }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
index c5e71fc..e5f0367 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
@@ -38,6 +38,7 @@
 import {GrAccountChip} from '../gr-account-chip/gr-account-chip';
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 import {PaperInputElementExt} from '../../../types/types';
+import {fire, EventType} from '../../../utils/event-util';
 
 const VALID_EMAIL_ALERT = 'Please input a valid email.';
 
@@ -256,13 +257,7 @@
         // Repopulate the input with what the user tried to enter and have
         // a toast tell them why they can't enter it.
         this.$.entry.setText(item);
-        this.dispatchEvent(
-          new CustomEvent('show-alert', {
-            detail: {message: VALID_EMAIL_ALERT},
-            bubbles: true,
-            composed: true,
-          })
-        );
+        fire(this, EventType.SHOW_ALERT, VALID_EMAIL_ALERT);
         return false;
       } else {
         const account = {email: item, _pendingAdd: true};
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index e4d520f..48fbb69 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -60,6 +60,7 @@
   UIRobot,
 } from '../../../utils/comment-util';
 import {OpenFixPreviewEventDetail} from '../../../types/events';
+import {fire, EventType} from '../../../utils/event-util';
 
 const STORAGE_DEBOUNCE_INTERVAL = 400;
 const TOAST_DEBOUNCE_INTERVAL = 200;
@@ -850,13 +851,7 @@
         // Note: the event is fired on the body rather than this element because
         // this element may not be attached by the time this executes, in which
         // case the event would not bubble.
-        document.body.dispatchEvent(
-          new CustomEvent('show-alert', {
-            detail: {message},
-            bubbles: true,
-            composed: true,
-          })
-        );
+        fire(document.body, EventType.SHOW_ALERT, message);
       },
       TOAST_DEBOUNCE_INTERVAL
     );
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
index 90aaa9f..71287a7 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -24,6 +24,7 @@
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {customElement, property} from '@polymer/decorators';
 import {htmlTemplate} from './gr-editable-content_html';
+import {fire, EventType} from '../../../utils/event-util';
 
 const RESTORED_MESSAGE = 'Content restored from a previous edit.';
 const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
@@ -149,13 +150,7 @@
       );
       if (storedContent?.message) {
         content = storedContent.message;
-        this.dispatchEvent(
-          new CustomEvent('show-alert', {
-            detail: {message: RESTORED_MESSAGE},
-            bubbles: true,
-            composed: true,
-          })
-        );
+        fire(this, EventType.SHOW_ALERT, RESTORED_MESSAGE);
       }
     }
     if (!content) {