Commit 680d9b69 authored by Robert Knight's avatar Robert Knight

Retry requests to /api endpoint

Handle requests to the /api endpoint that fail with network errors in
the same way as failed requests to the /app endpoint by retrying the
request with exponential backoff until it succeeds.

Otherwise, if the request fails then `store.(Annotation|Search)Resource`
are not populated and code that tries to make API requests fails.
parent bc6b650e
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
var angular = require('angular'); var angular = require('angular');
var retryUtil = require('./retry-util');
function prependTransform(defaults, transform) { function prependTransform(defaults, transform) {
// We can't guarantee that the default transformation is an array // We can't guarantee that the default transformation is an array
var result = angular.isArray(defaults) ? defaults.slice(0) : [defaults]; var result = angular.isArray(defaults) ? defaults.slice(0) : [defaults];
...@@ -57,10 +59,14 @@ function encodeUriQuery(val) { ...@@ -57,10 +59,14 @@ function encodeUriQuery(val) {
// then proceed to parse as a delimiter in the query string. To avoid this // then proceed to parse as a delimiter in the query string. To avoid this
// problem we use a very conservative encoder, found above. // problem we use a very conservative encoder, found above.
function serializeParams(params) { function serializeParams(params) {
if (!params) return ''; if (!params) {
return '';
}
var parts = []; var parts = [];
forEachSorted(params, function(value, key) { forEachSorted(params, function(value, key) {
if (value === null || typeof value === 'undefined') return; if (value === null || typeof value === 'undefined') {
return;
}
if (angular.isArray(value)) { if (angular.isArray(value)) {
angular.forEach(value, function(v, k) { angular.forEach(value, function(v, k) {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
...@@ -99,10 +105,9 @@ function store($http, $resource, settings) { ...@@ -99,10 +105,9 @@ function store($http, $resource, settings) {
}; };
// We call the API root and it gives back the actions it provides. // We call the API root and it gives back the actions it provides.
instance.$resolved = false; instance.$promise = retryUtil.retryPromiseOperation(function () {
instance.$promise = $http.get(settings.apiUrl) return $http.get(settings.apiUrl);
.finally(function () { instance.$resolved = true; }) }).then(function (response) {
.then(function (response) {
var links = response.data.links; var links = response.data.links;
// N.B. in both cases below we explicitly override the default `get` // N.B. in both cases below we explicitly override the default `get`
......
'use strict'; 'use strict';
var inject = angular.mock.inject; var angular = require('angular');
var module = angular.mock.module; var proxyquire = require('proxyquire');
var util = require('./util');
describe('store', function () { describe('store', function () {
var $httpBackend = null; var $httpBackend = null;
...@@ -10,12 +12,19 @@ describe('store', function () { ...@@ -10,12 +12,19 @@ describe('store', function () {
before(function () { before(function () {
angular.module('h', ['ngResource']) angular.module('h', ['ngResource'])
.service('store', require('../store')); .service('store', proxyquire('../store', util.noCallThru({
angular: angular,
'./retry-util': {
retryPromiseOperation: function (fn) {
return fn();
},
},
})));
}); });
beforeEach(module('h')); beforeEach(angular.mock.module('h'));
beforeEach(module(function ($provide) { beforeEach(angular.mock.module(function ($provide) {
sandbox = sinon.sandbox.create(); sandbox = sinon.sandbox.create();
$provide.value('settings', {apiUrl: 'http://example.com/api'}); $provide.value('settings', {apiUrl: 'http://example.com/api'});
})); }));
...@@ -26,7 +35,7 @@ describe('store', function () { ...@@ -26,7 +35,7 @@ describe('store', function () {
sandbox.restore(); sandbox.restore();
}); });
beforeEach(inject(function ($q, _$httpBackend_, _store_) { beforeEach(angular.mock.inject(function ($q, _$httpBackend_, _store_) {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
store = _store_; store = _store_;
...@@ -50,8 +59,8 @@ describe('store', function () { ...@@ -50,8 +59,8 @@ describe('store', function () {
})); }));
it('reads the operations from the backend', function () { it('reads the operations from the backend', function () {
assert.isFunction(store.AnnotationResource, 'expected store.AnnotationResource to be a function') assert.isFunction(store.AnnotationResource);
assert.isFunction(store.SearchResource, 'expected store.SearchResource to be a function') assert.isFunction(store.SearchResource);
}); });
it('saves a new annotation', function () { it('saves a new annotation', function () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment