Merge "Submit requirements - fix hovercard issue"
diff --git a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
index 5e9ce97..b060d3e 100644
--- a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
+++ b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
@@ -26,13 +26,6 @@
       "GerritBackendRequestFeature__remove_revision_etag";
 
   /**
-   * Whether git pushes are cancelled if the client disconnects or the configured receive.timeout is
-   * exceeded.
-   */
-  public static final String GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_PUSH_CANCELLATION =
-      "GerritBackendRequestFeature__enable_push_cencallation";
-
-  /**
    * Allow legacy {@link com.google.gerrit.entities.SubmitRecord}s to be converted and returned as
    * submit requirements by the {@link
    * com.google.gerrit.server.project.SubmitRequirementsEvaluator}.
diff --git a/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index 3faafd1..786f96e 100644
--- a/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -24,7 +24,6 @@
 import com.google.gerrit.server.CancellationMetrics;
 import com.google.gerrit.server.cancellation.RequestStateProvider;
 import com.google.gerrit.server.experiments.ExperimentFeatures;
-import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.io.IOException;
@@ -469,12 +468,6 @@
 
   @Override
   public void checkIfCancelled(OnCancelled onCancelled) {
-    if (taskKind == TaskKind.RECEIVE_COMMITS
-        && !experimentFeatures.isFeatureEnabled(
-            ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_PUSH_CANCELLATION)) {
-      return;
-    }
-
     if (clientDisconnected) {
       onCancelled.onCancel(RequestStateProvider.Reason.CLIENT_CLOSED_REQUEST, /* message= */ null);
     } else if (deadlineExceeded) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java b/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java
index c5ceea0..ed5e559 100644
--- a/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java
@@ -28,7 +28,6 @@
 import com.google.gerrit.server.cancellation.RequestCancelledException;
 import com.google.gerrit.server.cancellation.RequestStateProvider;
 import com.google.gerrit.server.events.CommitReceivedEvent;
-import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidationListener;
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
@@ -582,9 +581,6 @@
 
   @Test
   @GerritConfig(name = "receive.timeout", value = "1ms")
-  @GerritConfig(
-      name = "experiments.enabled",
-      value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_PUSH_CANCELLATION)
   public void abortPushIfTimeoutExceeded() throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     PushOneCommit.Result r = push.to("refs/for/master");
@@ -593,18 +589,7 @@
 
   @Test
   @GerritConfig(name = "receive.timeout", value = "1ms")
-  public void pushNotAbortedIfTimeoutExceededAndExperimentNotEnabled() throws Exception {
-    PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
-    PushOneCommit.Result r = push.to("refs/for/master");
-    r.assertOkStatus();
-  }
-
-  @Test
-  @GerritConfig(name = "receive.timeout", value = "1ms")
   @GerritConfig(name = "deadline.default.timeout", value = "10s")
-  @GerritConfig(
-      name = "experiments.enabled",
-      value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_PUSH_CANCELLATION)
   public void receiveTimeoutTakesPrecedence() throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     PushOneCommit.Result r = push.to("refs/for/master");
@@ -674,9 +659,6 @@
 
   @Test
   @GerritConfig(name = "receive.timeout", value = "1ms")
-  @GerritConfig(
-      name = "experiments.enabled",
-      value = ExperimentFeaturesConstants.GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_PUSH_CANCELLATION)
   public void clientProvidedDeadlineOnPushDoesntOverrideServerTimeout() throws Exception {
     List<String> pushOptions = new ArrayList<>();
     pushOptions.add("deadline=10m");
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts
index a09c1c3..a10bdda 100644
--- a/polygerrit-ui/app/constants/reporting.ts
+++ b/polygerrit-ui/app/constants/reporting.ts
@@ -23,6 +23,7 @@
   VISIBILILITY_VISIBLE = 'Visibility changed to visible',
   EXTENSION_DETECTED = 'Extension detected',
   PLUGINS_INSTALLED = 'Plugins installed',
+  PLUGINS_FAILED = 'Some plugins failed to load',
   USER_REFERRED_FROM = 'User referred from',
 }
 
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 5b6a195..2175e04 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -28,6 +28,7 @@
 import {unique} from '../../../utils/common-util';
 import {
   extractAssociatedLabels,
+  hasVotes,
   iconForStatus,
 } from '../../../utils/label-util';
 import {fontStyles} from '../../../styles/gr-font-styles';
@@ -162,11 +163,23 @@
 
   renderVotes(requirement: SubmitRequirementResultInfo) {
     const requirementLabels = extractAssociatedLabels(requirement);
-    const labels = this.change?.labels ?? {};
+    const allLabels = this.change?.labels ?? {};
+    const associatedLabels = Object.keys(allLabels).filter(label =>
+      requirementLabels.includes(label)
+    );
 
-    return Object.keys(labels)
-      .filter(label => requirementLabels.includes(label))
-      .map(label => this.renderLabelVote(label, labels));
+    const everyAssociatedLabelsIsWithoutVotes = associatedLabels.every(
+      label => {
+        const labelInfo = allLabels[label];
+        if (!isDetailedLabelInfo(labelInfo)) return true;
+        return !hasVotes(labelInfo);
+      }
+    );
+    if (everyAssociatedLabelsIsWithoutVotes) return html`No votes`;
+
+    return associatedLabels.map(label =>
+      this.renderLabelVote(label, allLabels)
+    );
   }
 
   renderLabelVote(label: string, labels: LabelNameToInfoMap) {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
index 6fc8f1b..7c99480 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
@@ -123,7 +123,10 @@
     });
 
     this.awaitPluginsLoaded().then(() => {
-      this._getReporting().pluginsLoaded(this._getAllInstalledPluginNames());
+      const loaded = this.getPluginsByState(PluginState.LOADED);
+      const failed = this.getPluginsByState(PluginState.LOAD_FAILED);
+      this._getReporting().pluginsLoaded(loaded.map(p => p.name));
+      this._getReporting().pluginsFailed(failed.map(p => p.name));
     });
   }
 
@@ -140,14 +143,8 @@
     return url.pathname && url.pathname.endsWith(suffix);
   }
 
-  _getAllInstalledPluginNames() {
-    const installedPlugins = [];
-    for (const plugin of this._plugins.values()) {
-      if (plugin.state === PluginState.LOADED) {
-        installedPlugins.push(plugin.name);
-      }
-    }
-    return installedPlugins;
+  private getPluginsByState(state: PluginState) {
+    return [...this._plugins.values()].filter(p => p.state === state);
   }
 
   install(
@@ -190,16 +187,9 @@
     }
   }
 
-  // The polygerrit uses version of sinon where you can't stub getter,
-  // declare it as a function here
   arePluginsLoaded() {
-    // As the size of plugins is relatively small,
-    // so the performance of this check should be reasonable
     if (!this._pluginListLoaded) return false;
-    for (const plugin of this._plugins.values()) {
-      if (plugin.state === PluginState.PENDING) return false;
-    }
-    return true;
+    return this.getPluginsByState(PluginState.PENDING).length === 0;
   }
 
   _checkIfCompleted() {
@@ -214,15 +204,14 @@
   }
 
   _timeout() {
-    const pendingPlugins = [];
-    for (const plugin of this._plugins.values()) {
-      if (plugin.state === PluginState.PENDING) {
-        this._updatePluginState(plugin.url, PluginState.LOAD_FAILED);
-        this._checkIfCompleted();
-        pendingPlugins.push(plugin.url);
-      }
+    const pending = this.getPluginsByState(PluginState.PENDING);
+    for (const plugin of pending) {
+      this._updatePluginState(plugin.url, PluginState.LOAD_FAILED);
     }
-    return `Timeout when loading plugins: ${pendingPlugins.join(',')}`;
+    this._checkIfCompleted();
+    return `Timeout when loading plugins: ${pending
+      .map(p => p.name)
+      .join(',')}`;
   }
 
   _failToLoad(message: string, pluginUrl?: string) {
@@ -252,6 +241,7 @@
         plugin: null,
       });
     }
+    console.info(`Plugin ${key} ${state}`);
     return this._plugins.get(key)!;
   }
 
@@ -259,7 +249,6 @@
     const pluginObj = this._updatePluginState(url, PluginState.LOADED);
     pluginObj.plugin = plugin;
     this._getReporting().pluginLoaded(plugin.getPluginName() || url);
-    console.info(`Plugin ${plugin.getPluginName() || url} installed.`);
     this._checkIfCompleted();
   }
 
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
index 1445a59..06f1a0c 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
@@ -57,6 +57,7 @@
   reportExtension(name: string): void;
   pluginLoaded(name: string): void;
   pluginsLoaded(pluginsList?: string[]): void;
+  pluginsFailed(pluginsList?: string[]): void;
   error(err: Error, reporter?: string, details?: EventDetails): void;
   /**
    * Reset named timer.
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index 0df7d12..65a5784 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -636,6 +636,18 @@
     );
   }
 
+  pluginsFailed(pluginsList?: string[]) {
+    if (!pluginsList || pluginsList.length === 0) return;
+    this.reporter(
+      LIFECYCLE.TYPE,
+      LIFECYCLE.CATEGORY.PLUGINS_INSTALLED,
+      LifeCycle.PLUGINS_FAILED,
+      undefined,
+      {pluginsList: pluginsList || []},
+      true
+    );
+  }
+
   /**
    * Reset named Timing.
    */
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
index 13461bf..337cf2f 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
@@ -56,6 +56,7 @@
   },
   pluginLoaded: () => {},
   pluginsLoaded: () => {},
+  pluginsFailed: () => {},
   recordDraftInteraction: () => {},
   reporter: () => {},
   reportErrorDialog: (message: string) => {
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 16e6803..066c7cd 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -121,6 +121,13 @@
   return label.all?.filter(x => x._account_id === account._account_id)[0];
 }
 
+export function hasVotes(labelInfo: DetailedLabelInfo) {
+  return (labelInfo.all ?? []).some(
+    approval =>
+      getLabelStatus(labelInfo, approval.value) !== LabelStatus.NEUTRAL
+  );
+}
+
 export function labelCompare(labelName1: string, labelName2: string) {
   if (labelName1 === CODE_REVIEW && labelName2 === CODE_REVIEW) return 0;
   if (labelName1 === CODE_REVIEW) return -1;