Merge "Measure and report time spent loading change data"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 29ffec8..cb9f4c5 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -57,6 +57,8 @@
     UNIFIED: 'UNIFIED_DIFF',
   };
 
+  const CHANGE_DATA_TIMING_LABEL = 'ChangeDataLoaded';
+
   Polymer({
     is: 'gr-change-view',
 
@@ -624,6 +626,8 @@
       this.$.fileList.collapseAllDiffs();
       this._patchRange = patchRange;
 
+      // If the change has already been loaded and the parameter change is only
+      // in the patch range, then don't do a full reload.
       if (this._initialLoadComplete && patchChanged) {
         if (patchRange.patchNum == null) {
           patchRange.patchNum = this.computeLatestPatchNum(this._allPatchSets);
@@ -637,7 +641,7 @@
       this._changeNum = value.changeNum;
       this.$.relatedChanges.clear();
 
-      this._reload().then(() => {
+      this._reload(true).then(() => {
         this._performPostLoadTasks();
       });
     },
@@ -651,7 +655,6 @@
     },
 
     _performPostLoadTasks() {
-      this.$.relatedChanges.reload();
       this._maybeShowReplyDialog();
       this._maybeShowRevertDialog();
 
@@ -1199,43 +1202,102 @@
           });
     },
 
-    _reload() {
+    /**
+     * Reload the change.
+     * @param {boolean=} opt_reloadRelatedChanges Reloads the related chanegs
+     *     when true.
+     * @return {Promise} A promise that resolves when the core data has loaded.
+     *     Some non-core data loading may still be in-flight when the core data
+     *     promise resolves.
+     */
+    _reload(opt_reloadRelatedChanges) {
       this._loading = true;
       this._relatedChangesCollapsed = true;
 
-      const detailCompletes = this._getChangeDetail().then(() => {
-        this._loading = false;
-        this._getProjectConfig();
-      });
+      // Array to house all promises related to data requests.
+      const allDataPromises = [];
 
-      this._reloadComments();
+      // Resolves when the change detail and the edit patch set (if available)
+      // are loaded.
+      const detailCompletes = this._getChangeDetail();
+      allDataPromises.push(detailCompletes);
 
-      let reloadPromise;
+      // Resolves when the loading flag is set to false, meaning that some
+      // change content may start appearing.
+      const loadingFlagSet = detailCompletes
+          .then(() => { this._loading = false; });
 
+      // Resolves when the project config has loaded.
+      const projectConfigLoaded = detailCompletes
+          .then(() => this._getProjectConfig());
+      allDataPromises.push(projectConfigLoaded);
+
+      // Resolves when change comments have loaded (comments, drafts and robot
+      // comments).
+      const commentsLoaded = this._reloadComments();
+      allDataPromises.push(commentsLoaded);
+
+      let coreDataPromise;
+
+      // If the patch number is specified
       if (this._patchRange.patchNum) {
-        reloadPromise = Promise.all([
-          this._reloadPatchNumDependentResources(),
-          detailCompletes,
-        ]).then(() => {
-          return Promise.all([
-            this._getMergeability(),
-            this.$.actions.reload(),
-          ]);
-        });
+        // Because a specific patchset is specified, reload the resources that
+        // are keyed by patch number or patch range.
+        const patchResourcesLoaded = this._reloadPatchNumDependentResources();
+        allDataPromises.push(patchResourcesLoaded);
+
+        // Promise resolves when the change detail and patch dependent resources
+        // have loaded.
+        const detailAndPatchResourcesLoaded =
+            Promise.all([patchResourcesLoaded, loadingFlagSet]);
+
+        // Promise resolves when mergeability information has loaded.
+        const mergeabilityLoaded = detailAndPatchResourcesLoaded
+            .then(() => this._getMergeability());
+        allDataPromises.push(mergeabilityLoaded);
+
+        // Promise resovles when the change actions have loaded.
+        const actionsLoaded = detailAndPatchResourcesLoaded
+            .then(() => this.$.actions.reload());
+        allDataPromises.push(actionsLoaded);
+
+        // The core data is loaded when both mergeability and actions are known.
+        coreDataPromise = Promise.all([mergeabilityLoaded, actionsLoaded]);
       } else {
-        // The patch number is reliant on the change detail request.
-        reloadPromise = detailCompletes.then(() => {
-          this.$.fileList.reload();
-          if (!this._latestCommitMessage) {
-            this._getLatestCommitMessage();
-          }
-          return this._getMergeability();
+        // Resolves when the file list has loaded.
+        const fileListReload = loadingFlagSet
+            .then(() => this.$.fileList.reload());
+        allDataPromises.push(fileListReload);
+
+        const latestCommitMessageLoaded = loadingFlagSet.then(() => {
+          // If the latest commit message is known, there is nothing to do.
+          if (this._latestCommitMessage) { return Promise.resolve(); }
+          return this._getLatestCommitMessage();
         });
+        allDataPromises.push(latestCommitMessageLoaded);
+
+        // Promise resolves when mergeability information has loaded.
+        const mergeabilityLoaded = loadingFlagSet
+            .then(() => this._getMergeability());
+        allDataPromises.push(mergeabilityLoaded);
+
+        // Core data is loaded when mergeability has been loaded.
+        coreDataPromise = mergeabilityLoaded;
       }
 
-      return reloadPromise.then(() => {
-        this.$.reporting.changeDisplayed();
+      if (opt_reloadRelatedChanges) {
+        const relatedChangesLoaded = coreDataPromise
+            .then(() => this.$.relatedChanges.reload());
+        allDataPromises.push(relatedChangesLoaded);
+      }
+
+      this.$.reporting.time(CHANGE_DATA_TIMING_LABEL);
+      Promise.all(allDataPromises).then(() => {
+        this.$.reporting.timeEnd(CHANGE_DATA_TIMING_LABEL);
       });
+
+      return coreDataPromise
+          .then(() => { this.$.reporting.changeDisplayed(); });
     },
 
     /**