Better reporting of plugins that fail to load

Google-Bug-Id: b/200985287
Change-Id: I9f76213b5e0102c0e3e85d92db59b51bbf4027ec
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/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) => {