Redirect from change edit if change is merged or abandoned

Also display a notification telling the user why.

Change-Id: I9b8f9cf7da7992274fe05e46be481246543d4c57
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 2ede186e..4dc9656 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
@@ -76,6 +76,8 @@
   PatchSet,
 } from '../../../utils/patch-set-util';
 import {
+  changeIsMerged,
+  changeIsAbandoned,
   changeStatuses,
   isCc,
   isOwner,
@@ -1863,9 +1865,23 @@
    * case an edit exists.
    */
   _processEdit(change: ParsedChangeInfo, edit?: EditInfo | false) {
+    if (
+      (changeIsMerged(change) || changeIsAbandoned(change)) &&
+      this._editMode
+    ) {
+      fireAlert(
+        this,
+        'Change edits cannot be created if change is merged or abandoned. Redirected to non edit mode.'
+      );
+      GerritNav.navigateToChange(change);
+      return;
+    }
+
     if (!edit) return;
+
     if (!this._patchRange)
       throw new Error('missing required _patchRange property');
+
     if (!edit.commit.commit) throw new Error('undefined edit.commit.commit');
     const changeWithEdit = change;
     if (changeWithEdit.revisions)
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 aeb6500..8172f41 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
@@ -43,6 +43,7 @@
 import {ErrorCallback} from '../../../api/rest';
 import {assertIsDefined} from '../../../utils/common-util';
 import {debounce, DelayedTask} from '../../../utils/async-util';
+import {changeIsMerged, changeIsAbandoned} from '../../../utils/change-util';
 
 const RESTORED_MESSAGE = 'Content restored from a previous edit.';
 const SAVING_MESSAGE = 'Saving changes...';
@@ -74,7 +75,7 @@
   @property({type: Object, observer: '_paramsChanged'})
   params?: GenerateUrlEditViewParameters;
 
-  @property({type: Object})
+  @property({type: Object, observer: '_editChange'})
   _change?: ChangeInfo | null;
 
   @property({type: Number})
@@ -194,6 +195,16 @@
     });
   }
 
+  _editChange(value?: ChangeInfo | null) {
+    if (!changeIsMerged(value) && !changeIsAbandoned(value)) return;
+    if (!value) return;
+    fireAlert(
+      this,
+      'Change edits cannot be created if change is merged or abandoned. Redirected to non edit mode.'
+    );
+    GerritNav.navigateToChange(value);
+  }
+
   _handlePathChanged(e: CustomEvent<string>) {
     // TODO(TS) could be cleaned up, it was added for type requirements
     if (this._changeNum === undefined || !this._path) {
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index 3dac8d3..c54c099 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -131,10 +131,20 @@
   return `${getBaseUrl()}/c/${changeNum}`;
 }
 
-export function changeIsOpen(change?: ChangeInfo | ParsedChangeInfo) {
+export function changeIsOpen(change?: ChangeInfo | ParsedChangeInfo | null) {
   return change?.status === ChangeStatus.NEW;
 }
 
+export function changeIsMerged(change?: ChangeInfo | ParsedChangeInfo | null) {
+  return change?.status === ChangeStatus.MERGED;
+}
+
+export function changeIsAbandoned(
+  change?: ChangeInfo | ParsedChangeInfo | null
+) {
+  return change?.status === ChangeStatus.ABANDONED;
+}
+
 export function changeStatuses(
   change: ChangeInfo,
   opt_options?: ChangeStatusesOptions
diff --git a/polygerrit-ui/app/utils/change-util_test.ts b/polygerrit-ui/app/utils/change-util_test.ts
index 8232e56..ccec27e 100644
--- a/polygerrit-ui/app/utils/change-util_test.ts
+++ b/polygerrit-ui/app/utils/change-util_test.ts
@@ -27,6 +27,9 @@
 } from '../types/common';
 import {
   changeBaseURL,
+  changeIsOpen,
+  changeIsMerged,
+  changeIsAbandoned,
   changePath,
   changeStatuses,
   isRemovableReviewer,
@@ -195,4 +198,43 @@
     };
     assert.equal(isRemovableReviewer(change, reviewer), false);
   });
+
+  test('changeIsOpen', () => {
+    const change = {
+      ...createChange(),
+      revisions: createRevisions(1),
+      current_revision: 'rev1' as CommitId,
+      status: ChangeStatus.NEW,
+      mergeable: false,
+    };
+    assert.isTrue(changeIsOpen(change));
+    change.status = ChangeStatus.MERGED;
+    assert.isFalse(changeIsOpen(change));
+  });
+
+  test('changeIsMerged', () => {
+    const change = {
+      ...createChange(),
+      revisions: createRevisions(1),
+      current_revision: 'rev1' as CommitId,
+      status: ChangeStatus.MERGED,
+      mergeable: false,
+    };
+    assert.isTrue(changeIsMerged(change));
+    change.status = ChangeStatus.NEW;
+    assert.isFalse(changeIsMerged(change));
+  });
+
+  test('changeIsAbandoned', () => {
+    const change = {
+      ...createChange(),
+      revisions: createRevisions(1),
+      current_revision: 'rev1' as CommitId,
+      status: ChangeStatus.ABANDONED,
+      mergeable: false,
+    };
+    assert.isTrue(changeIsAbandoned(change));
+    change.status = ChangeStatus.NEW;
+    assert.isFalse(changeIsAbandoned(change));
+  });
 });