From 744b5a6f15b3a779c285bacabb505ed1c4345c64 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 4 Apr 2019 15:25:32 +0200
Subject: [PATCH] when plugin listener crash system notifies user and plugin
 about a problem

---
 CHANGELOG                                     |  2 ++
 .../src/main/js/plugin/MinervaPluginProxy.js  | 24 ++++++++++++++++---
 frontend-js/src/main/js/plugin/Plugin.js      |  1 +
 frontend-js/src/test/js/plugin/Plugin-test.js | 11 +++++++++
 ...rash.js&version=0.0.1&token=MOCK_TOKEN_ID& |  8 +++++++
 .../testFiles/plugin/listener-crash.js        | 23 ++++++++++++++++++
 6 files changed, 66 insertions(+), 3 deletions(-)
 create mode 100644 frontend-js/testFiles/apiCalls/plugins/POST_hash=a794c40b7ed8f56406f66f2913543915&isPublic=false&name=test%20plugin&url=.%2FtestFiles%2Fplugin%2Flistener-crash.js&version=0.0.1&token=MOCK_TOKEN_ID&
 create mode 100644 frontend-js/testFiles/plugin/listener-crash.js

diff --git a/CHANGELOG b/CHANGELOG
index 420f5aab10..08487c32b0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -31,6 +31,8 @@ minerva (12.3.0~alpha.0) unstable; urgency=low
     (#591)
   * Small improvement: uploading sbml file should automatically discover a file
     type (#784)
+  * Small improvement: when plugin listeners crash the system notifies user 
+    about problem with a plugin (#767)
   * Bug fix: progress bar of gene genome mapping upload is refreshing properly
     (#728)
   * Bug fix: when editing project Disease and Organism could not be removed
diff --git a/frontend-js/src/main/js/plugin/MinervaPluginProxy.js b/frontend-js/src/main/js/plugin/MinervaPluginProxy.js
index 1e1b8673c4..feb8595972 100644
--- a/frontend-js/src/main/js/plugin/MinervaPluginProxy.js
+++ b/frontend-js/src/main/js/plugin/MinervaPluginProxy.js
@@ -12,6 +12,8 @@ var Configuration = require('../Configuration');
 var Bounds = require('../map/canvas/Bounds');
 var Point = require('../map/canvas/Point');
 
+var GuiConnector = require('../GuiConnector');
+
 // noinspection JSUnusedLocalSymbols
 var logger = require('../logger');
 
@@ -453,7 +455,7 @@ function createProjectMap(options) {
         return dbOverlay.searchByQuery(params.query, params.perfect, params.fitBounds);
       } else if (dbOverlay instanceof SearchDbOverlay) {
         var zoom = params.zoom;
-        if (zoom ===undefined) {
+        if (zoom === undefined) {
           zoom = map.getSubmapById(params.modelId).getZoom();
         }
         return dbOverlay.searchByCoordinates({
@@ -582,10 +584,26 @@ function createProjectMap(options) {
         throw new Error("Invalid argument");
       }
 
+      var wrapper = function (e) {
+        try {
+          return Promise.resolve(listenerWrapper(e)).catch(function (error) {
+            GuiConnector.warn("Plugin " + options.plugin.getName() + " crashed");
+            if (typeof options.plugin.getLoadedPluginData().notifyError === "function") {
+              options.plugin.getLoadedPluginData().notifyError({listener: param, data: e, error: error});
+            }
+          });
+        } catch (error) {
+          GuiConnector.warn("Plugin " + options.plugin.getName() + " crashed");
+          if (typeof options.plugin.getLoadedPluginData().notifyError === "function") {
+            options.plugin.getLoadedPluginData().notifyError({listener: param, data: e, error: error});
+          }
+        }
+      };
+
       for (var i = 0; i < objects.length; i++) {
         var object = objects[i];
-        object.addListener(listenerType, listenerWrapper);
-        listenersData.push({listener: param.callback, wrapper: listenerWrapper, object: object, type: listenerType});
+        object.addListener(listenerType, wrapper);
+        listenersData.push({listener: param.callback, wrapper: wrapper, object: object, type: listenerType});
       }
     },
     /**
diff --git a/frontend-js/src/main/js/plugin/Plugin.js b/frontend-js/src/main/js/plugin/Plugin.js
index 409d9d68c7..c513e2839d 100644
--- a/frontend-js/src/main/js/plugin/Plugin.js
+++ b/frontend-js/src/main/js/plugin/Plugin.js
@@ -24,6 +24,7 @@ var pluginId = 0;
  * @typedef {Object} UserPluginObject
  * @property {function(Object):void} register
  * @property {function():void} unregister
+ * @property {function(Object):void} [notifyError]
  * @property {function():string} getName
  * @property {function():string} getVersion
  * @property {function():(number|string)|number|string} minWidth
diff --git a/frontend-js/src/test/js/plugin/Plugin-test.js b/frontend-js/src/test/js/plugin/Plugin-test.js
index adbf4c9685..ac0671033a 100644
--- a/frontend-js/src/test/js/plugin/Plugin-test.js
+++ b/frontend-js/src/test/js/plugin/Plugin-test.js
@@ -5,6 +5,7 @@ require("../mocha-config");
 // noinspection JSUnusedLocalSymbols
 var Promise = require("bluebird");
 var Plugin = require('../../../main/js/plugin/Plugin');
+var Point = require('../../../main/js/map/canvas/Point');
 var ServerConnector = require('../ServerConnector-mock');
 
 var logger = require('../logger');
@@ -106,4 +107,14 @@ describe('Plugin', function () {
       });
     });
   });
+  it('plugin listener crash', function () {
+    var map = helper.createCustomMap();
+
+    var plugin = createPlugin("./testFiles/plugin/listener-crash.js", map);
+    return plugin.load().then(function () {
+      return map.callListeners("onCenterChanged", new Point(0, 0));
+    }).then(function () {
+      assert.equal(1, logger.getWarnings().length);
+    });
+  });
 });
diff --git a/frontend-js/testFiles/apiCalls/plugins/POST_hash=a794c40b7ed8f56406f66f2913543915&isPublic=false&name=test%20plugin&url=.%2FtestFiles%2Fplugin%2Flistener-crash.js&version=0.0.1&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/plugins/POST_hash=a794c40b7ed8f56406f66f2913543915&isPublic=false&name=test%20plugin&url=.%2FtestFiles%2Fplugin%2Flistener-crash.js&version=0.0.1&token=MOCK_TOKEN_ID&
new file mode 100644
index 0000000000..02a0838194
--- /dev/null
+++ b/frontend-js/testFiles/apiCalls/plugins/POST_hash=a794c40b7ed8f56406f66f2913543915&isPublic=false&name=test%20plugin&url=.%2FtestFiles%2Fplugin%2Flistener-crash.js&version=0.0.1&token=MOCK_TOKEN_ID&
@@ -0,0 +1,8 @@
+{
+    "hash": "d5d652ac0e0f6467d4cb6a742f99d3f7",
+    "name": "test plugin",
+    "urls": [
+        "./testFiles/plugin-invalid/invalid_register.js"
+    ],
+    "version": "0.0.1"
+}
\ No newline at end of file
diff --git a/frontend-js/testFiles/plugin/listener-crash.js b/frontend-js/testFiles/plugin/listener-crash.js
new file mode 100644
index 0000000000..d93a9d0a89
--- /dev/null
+++ b/frontend-js/testFiles/plugin/listener-crash.js
@@ -0,0 +1,23 @@
+minervaDefine(function () {
+  return {
+    register: function (minervaObject) {
+      var options = {
+        object: "map",
+        type: "onCenterChanged",
+        callback: function () {
+          throw new Error("Let's crash");
+        }
+      };
+      minervaObject.project.map.addListener(options);
+    },
+    unregister: function () {
+      console.log("unregistering test plugin");
+    },
+    getName: function () {
+      return "test plugin";
+    },
+    getVersion: function () {
+      return "0.0.1";
+    }
+  };
+});
\ No newline at end of file
-- 
GitLab