Merge "Override equals in REST API data classes"
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
index f172ccc..e02b337 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
@@ -99,6 +99,9 @@
       padding-left: 0;
     }
   </style>
+  <template is="dom-if" if="[[_isNewChangeSummaryUiEnabled]]">
+    <h3 class="metadata-title">Submit requirements</h3>
+  </template>
   <template is="dom-repeat" items="[[_requirements]]">
     <gr-endpoint-decorator
       class="submit-requirement-endpoints"
@@ -126,9 +129,6 @@
       </div>
     </gr-endpoint-decorator>
   </template>
-  <template is="dom-if" if="[[_isNewChangeSummaryUiEnabled]]">
-    <h3 class="metadata-title">Submit requirements</h3>
-  </template>
   <template is="dom-repeat" items="[[_requiredLabels]]">
     <section>
       <div class="title">
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 304939c..3d7646f 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
@@ -2313,6 +2313,7 @@
                 return response;
               });
           }
+          // TODO: use returned Promise
           this.getRelatedChangesListExperimental()?.reload(
             relatedChangesPromise
           );
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
index 11f0fd3..e921979 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
@@ -16,9 +16,12 @@
  */
 import {html, nothing} from 'lit-html';
 import './gr-related-change';
+import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import '../../plugins/gr-endpoint-param/gr-endpoint-param';
+import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
 import {classMap} from 'lit-html/directives/class-map';
 import {GrLitElement} from '../../lit/gr-lit-element';
-import {customElement, property, css} from 'lit-element';
+import {customElement, property, css, internalProperty} from 'lit-element';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {
   SubmittedTogetherInfo,
@@ -32,7 +35,11 @@
 import {ParsedChangeInfo} from '../../../types/types';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {pluralize} from '../../../utils/string-util';
-import {getRevisionKey, isChangeInfo} from '../../../utils/change-util';
+import {
+  changeIsOpen,
+  getRevisionKey,
+  isChangeInfo,
+} from '../../../utils/change-util';
 
 /** What is the maximum number of shown changes in collapsed list? */
 const MAX_CHANGES_WHEN_COLLAPSED = 3;
@@ -46,13 +53,25 @@
   patchNum?: PatchSetNum;
 
   @property()
-  _submittedTogether?: SubmittedTogetherInfo = {
+  mergeable?: boolean;
+
+  @internalProperty()
+  submittedTogether?: SubmittedTogetherInfo = {
     changes: [],
     non_visible_changes: 0,
   };
 
-  @property()
-  _relatedResponse?: RelatedChangesInfo = {changes: []};
+  @internalProperty()
+  relatedChanges: RelatedChangeAndCommitInfo[] = [];
+
+  @internalProperty()
+  conflictingChanges: ChangeInfo[] = [];
+
+  @internalProperty()
+  cherryPickChanges: ChangeInfo[] = [];
+
+  @internalProperty()
+  sameTopicChanges: ChangeInfo[] = [];
 
   private readonly restApiService = appContext.restApiService;
 
@@ -62,6 +81,7 @@
       css`
         .note {
           color: var(--error-text-color);
+          margin-left: 1.2em;
         }
         section {
           margin-bottom: var(--spacing-m);
@@ -71,27 +91,26 @@
   }
 
   render() {
-    const relatedChanges = this._relatedResponse?.changes ?? [];
     let showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
-      relatedChanges.length,
-      relatedChanges.findIndex(relatedChange =>
+      this.relatedChanges.length,
+      this.relatedChanges.findIndex(relatedChange =>
         this._changesEqual(relatedChange, this.change)
       )
     );
     const connectedRevisions = this._computeConnectedRevisions(
       this.change,
       this.patchNum,
-      relatedChanges
+      this.relatedChanges
     );
     const relatedChangeSection = html` <section
       class="relatedChanges"
-      ?hidden=${!relatedChanges.length}
+      ?hidden=${!this.relatedChanges.length}
     >
       <gr-related-collapse
         title="Relation chain"
-        .length=${relatedChanges.length}
+        .length=${this.relatedChanges.length}
       >
-        ${relatedChanges.map(
+        ${this.relatedChanges.map(
           (change, index) =>
             html`<gr-related-change
               class="${classMap({
@@ -114,9 +133,9 @@
       </gr-related-collapse>
     </section>`;
 
-    const submittedTogetherChanges = this._submittedTogether?.changes ?? [];
+    const submittedTogetherChanges = this.submittedTogether?.changes ?? [];
     const countNonVisibleChanges =
-      this._submittedTogether?.non_visible_changes ?? 0;
+      this.submittedTogether?.non_visible_changes ?? 0;
     showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
       submittedTogetherChanges.length,
       submittedTogetherChanges.findIndex(relatedChange =>
@@ -126,7 +145,7 @@
     const submittedTogetherSection = html`<section
       id="submittedTogether"
       ?hidden=${!submittedTogetherChanges?.length &&
-      !this._submittedTogether?.non_visible_changes}
+      !this.submittedTogether?.non_visible_changes}
     >
       <gr-related-collapse
         title="Submitted together"
@@ -155,11 +174,106 @@
       </div>
     </section>`;
 
-    return html`${relatedChangeSection}${submittedTogetherSection}`;
+    showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
+      this.sameTopicChanges.length,
+      -1
+    );
+    const sameTopicSection = html`<section
+      id="sameTopic"
+      ?hidden=${!this.sameTopicChanges?.length}
+    >
+      <gr-related-collapse
+        title="Same topic"
+        .length=${this.sameTopicChanges.length}
+      >
+        ${this.sameTopicChanges.map(
+          (change, index) =>
+            html`<gr-related-change
+              class="${classMap({
+                ['show-when-collapsed']: showWhenCollapsedPredicate(index),
+              })}"
+              .change="${change}"
+              .href="${GerritNav.getUrlForChangeById(
+                change._number,
+                change.project
+              )}"
+              >${change.project}: ${change.branch}:
+              ${change.subject}</gr-related-change
+            >`
+        )}
+      </gr-related-collapse>
+    </section>`;
+
+    showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
+      this.conflictingChanges.length,
+      -1
+    );
+    const mergeConflictsSection = html`<section
+      id="mergeConflicts"
+      ?hidden=${!this.conflictingChanges?.length}
+    >
+      <gr-related-collapse
+        title="Merge conflicts"
+        .length=${this.conflictingChanges.length}
+      >
+        ${this.conflictingChanges.map(
+          (change, index) =>
+            html`<gr-related-change
+              class="${classMap({
+                ['show-when-collapsed']: showWhenCollapsedPredicate(index),
+              })}"
+              .change="${change}"
+              .href="${GerritNav.getUrlForChangeById(
+                change._number,
+                change.project
+              )}"
+              >${change.subject}</gr-related-change
+            >`
+        )}
+      </gr-related-collapse>
+    </section>`;
+
+    showWhenCollapsedPredicate = this.showWhenCollapsedPredicateFactory(
+      this.cherryPickChanges.length,
+      -1
+    );
+    const cherryPicksSection = html`<section
+      id="cherryPicks"
+      ?hidden=${!this.cherryPickChanges?.length}
+    >
+      <gr-related-collapse
+        title="Cherry picks"
+        .length=${this.cherryPickChanges.length}
+      >
+        ${this.cherryPickChanges.map(
+          (change, index) =>
+            html`<gr-related-change
+              class="${classMap({
+                ['show-when-collapsed']: showWhenCollapsedPredicate(index),
+              })}"
+              .change="${change}"
+              .href="${GerritNav.getUrlForChangeById(
+                change._number,
+                change.project
+              )}"
+              >${change.branch}: ${change.subject}</gr-related-change
+            >`
+        )}
+      </gr-related-collapse>
+    </section>`;
+
+    return html`<gr-endpoint-decorator name="related-changes-section">
+      <gr-endpoint-param name="change" value="[[change]]"></gr-endpoint-param>
+      <gr-endpoint-slot name="top"></gr-endpoint-slot>
+      ${relatedChangeSection} ${submittedTogetherSection} ${sameTopicSection}
+      ${mergeConflictsSection} ${cherryPicksSection}
+      <gr-endpoint-slot name="bottom"></gr-endpoint-slot>
+    </gr-endpoint-decorator>`;
   }
 
   showWhenCollapsedPredicateFactory(length: number, highlightIndex: number) {
     return (index: number) => {
+      if (highlightIndex === -1) return index < MAX_CHANGES_WHEN_COLLAPSED;
       if (highlightIndex === 0) return index <= MAX_CHANGES_WHEN_COLLAPSED - 1;
       if (highlightIndex === length - 1)
         return index >= length - MAX_CHANGES_WHEN_COLLAPSED;
@@ -171,21 +285,60 @@
   }
 
   reload(getRelatedChanges?: Promise<RelatedChangesInfo | undefined>) {
-    if (!this.change) return Promise.reject(new Error('change missing'));
+    const change = this.change;
+    if (!change) return Promise.reject(new Error('change missing'));
+    if (!this.patchNum) return Promise.reject(new Error('patchNum missing'));
+    if (!getRelatedChanges) {
+      getRelatedChanges = this.restApiService.getRelatedChanges(
+        change._number,
+        this.patchNum
+      );
+    }
     const promises: Array<Promise<void>> = [
+      getRelatedChanges.then(response => {
+        if (!response) {
+          throw new Error('getRelatedChanges returned undefined response');
+        }
+        this.relatedChanges = response?.changes ?? [];
+      }),
       this.restApiService
-        .getChangesSubmittedTogether(this.change._number)
+        .getChangesSubmittedTogether(change._number)
         .then(response => {
-          this._submittedTogether = response;
+          this.submittedTogether = response;
+        }),
+      this.restApiService
+        .getChangeCherryPicks(change.project, change.change_id, change._number)
+        .then(response => {
+          this.cherryPickChanges = response || [];
         }),
     ];
-    if (getRelatedChanges) {
+
+    // Get conflicts if change is open and is mergeable.
+    // Mergeable is output of restApiServict.getMergeable from gr-change-view
+    if (changeIsOpen(change) && this.mergeable) {
       promises.push(
-        getRelatedChanges.then(response => {
-          if (!response) {
-            throw new Error('getRelatedChanges returned undefined response');
+        this.restApiService
+          .getChangeConflicts(change._number)
+          .then(response => {
+            this.conflictingChanges = response ?? [];
+          })
+      );
+    }
+    if (change.topic) {
+      const changeTopic = change.topic;
+      promises.push(
+        this.restApiService.getConfig().then(config => {
+          if (config && !config.change.submit_whole_topic) {
+            return this.restApiService
+              .getChangesWithSameTopic(changeTopic, change._number)
+              .then(response => {
+                if (changeTopic === this.change?.topic) {
+                  this.sameTopicChanges = response ?? [];
+                }
+              });
           }
-          this._relatedResponse = response;
+          this.sameTopicChanges = [];
+          return Promise.resolve();
         })
       );
     }
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
index 8cb0638..9941fa9 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
@@ -193,6 +193,7 @@
               href$="[[_computeChangeURL(change._number, change.project)]]"
               class$="[[_computeLinkClass(change)]]"
               title$="[[change.subject]]"
+              on-click="_reportClick"
             >
               [[change.subject]]
             </a>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
index 95252cf..a3d038d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
@@ -120,12 +120,8 @@
    * Don't forget to also call disposeLayer().
    */
   createLayer(path: string, changeNum: number) {
-    if (!this.annotationCallback) return undefined;
-    const annotationLayer = new AnnotationLayer(
-      path,
-      changeNum,
-      this.annotationCallback
-    );
+    const callbackFn = this.annotationCallback || (() => {});
+    const annotationLayer = new AnnotationLayer(path, changeNum, callbackFn);
     this.annotationLayers.push(annotationLayer);
     return annotationLayer;
   }