Report network failures to plugins

Usage:

plugin.get(url).then(doStuff).catch(onError);
plugin.post(url, payload).then(doStuff).catch(onError);

onError will receive an error string, that is one of the following:
- error message from Gerrit REST API, eg. "Cherry pick failed: ..."
- error message from Fetch API, eg "TypeError: Failed to fetch"
- HTTP failure code, eg "404"

Bug: Issue 6595
Change-Id: I1a598fe2c249c8e87bc575359d51ad2e15431356
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index 761fe2a..1ff08ea 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -47,7 +47,7 @@
     setup(() => {
       sandbox = sinon.sandbox.create();
       getResponseObjectStub = sandbox.stub().returns(Promise.resolve());
-      sendStub = sandbox.stub().returns(Promise.resolve());
+      sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
       stub('gr-rest-api-interface', {
         getAccount() {
           return Promise.resolve({name: 'Judy Hopps'});
@@ -76,24 +76,47 @@
           'http://test.com/plugins/testplugin/static/test.js');
     });
 
-    test('get', done => {
-      const response = {foo: 'foo'};
-      getResponseObjectStub.returns(Promise.resolve(response));
-      plugin.get('/url', r => {
-        assert.isTrue(sendStub.calledWith('GET', '/url'));
-        assert.strictEqual(r, response);
-        done();
+    test('_send on failure rejects with response text', () => {
+      sendStub.returns(Promise.resolve(
+          {status: 400, text() {return Promise.resolve('text');}}));
+      return plugin._send().catch(r => {
+        assert.equal(r, 'text');
       });
     });
 
-    test('post', done => {
+    test('_send on failure without text rejects with code', () => {
+      sendStub.returns(Promise.resolve(
+          {status: 400, text() {return Promise.resolve(null);}}));
+      return plugin._send().catch(r => {
+        assert.equal(r, '400');
+      });
+    });
+
+    test('get', () => {
+      const response = {foo: 'foo'};
+      getResponseObjectStub.returns(Promise.resolve(response));
+      return plugin.get('/url', r => {
+        assert.isTrue(sendStub.calledWith('GET', '/url'));
+        assert.strictEqual(r, response);
+      });
+    });
+
+    test('get using Promise', () => {
+      const response = {foo: 'foo'};
+      getResponseObjectStub.returns(Promise.resolve(response));
+      return plugin.get('/url', r => 'rubbish').then(r => {
+        assert.isTrue(sendStub.calledWith('GET', '/url'));
+        assert.strictEqual(r, response);
+      });
+    });
+
+    test('post', () => {
       const payload = {foo: 'foo'};
       const response = {bar: 'bar'};
       getResponseObjectStub.returns(Promise.resolve(response));
-      plugin.post('/url', payload, r => {
+      return plugin.post('/url', payload, r => {
         assert.isTrue(sendStub.calledWith('POST', '/url', payload));
         assert.strictEqual(r, response);
-        done();
       });
     });
 
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index ac53ff1..912896f 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -106,18 +106,33 @@
     return this._url.origin + '/plugins/' + this._name + (opt_path || '/');
   };
 
-  Plugin.prototype._send = function(method, url, callback, opt_payload) {
-    return getRestAPI().send(method, url, opt_payload)
-        .then(getRestAPI().getResponseObject)
-        .then(callback);
+  Plugin.prototype._send = function(method, url, opt_callback, opt_payload) {
+    return getRestAPI().send(method, url, opt_payload).then(response => {
+      if (response.status < 200 || response.status >= 300) {
+        return response.text().then(text => {
+          if (text) {
+            return Promise.reject(text);
+          } else {
+            return Promise.reject(response.status);
+          }
+        });
+      } else {
+        return getRestAPI().getResponseObject(response);
+      }
+    }).then(response => {
+      if (opt_callback) {
+        opt_callback(response);
+      }
+      return response;
+    });
   };
 
-  Plugin.prototype.get = function(url, callback) {
-    return this._send('GET', url, callback);
+  Plugin.prototype.get = function(url, opt_callback) {
+    return this._send('GET', url, opt_callback);
   },
 
-  Plugin.prototype.post = function(url, payload, callback) {
-    return this._send('POST', url, callback, payload);
+  Plugin.prototype.post = function(url, payload, opt_callback) {
+    return this._send('POST', url, opt_callback, payload);
   },
 
   Plugin.prototype.changeActions = function() {