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 @@
var angular = require('angular');
var retryUtil = require('./retry-util');
function prependTransform(defaults, transform) {
// We can't guarantee that the default transformation is an array
var result = angular.isArray(defaults) ? defaults.slice(0) : [defaults];
......@@ -57,10 +59,14 @@ function encodeUriQuery(val) {
// then proceed to parse as a delimiter in the query string. To avoid this
// problem we use a very conservative encoder, found above.
function serializeParams(params) {
if (!params) return '';
if (!params) {
return '';
}
var parts = [];
forEachSorted(params, function(value, key) {
if (value === null || typeof value === 'undefined') return;
if (value === null || typeof value === 'undefined') {
return;
}
if (angular.isArray(value)) {
angular.forEach(value, function(v, k) {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
......@@ -99,29 +105,28 @@ function store($http, $resource, settings) {
};
// We call the API root and it gives back the actions it provides.
instance.$resolved = false;
instance.$promise = $http.get(settings.apiUrl)
.finally(function () { instance.$resolved = true; })
.then(function (response) {
var links = response.data.links;
// N.B. in both cases below we explicitly override the default `get`
// action because there is no way to provide defaultOptions to the default
// action.
instance.SearchResource = $resource(links.search.url, {}, {
get: angular.extend({url: links.search.url}, defaultOptions),
});
instance.AnnotationResource = $resource(links.annotation.read.url, {}, {
get: angular.extend(links.annotation.read, defaultOptions),
create: angular.extend(links.annotation.create, defaultOptions),
update: angular.extend(links.annotation.update, defaultOptions),
delete: angular.extend(links.annotation.delete, defaultOptions),
});
instance.$promise = retryUtil.retryPromiseOperation(function () {
return $http.get(settings.apiUrl);
}).then(function (response) {
var links = response.data.links;
// N.B. in both cases below we explicitly override the default `get`
// action because there is no way to provide defaultOptions to the default
// action.
instance.SearchResource = $resource(links.search.url, {}, {
get: angular.extend({url: links.search.url}, defaultOptions),
});
return instance;
instance.AnnotationResource = $resource(links.annotation.read.url, {}, {
get: angular.extend(links.annotation.read, defaultOptions),
create: angular.extend(links.annotation.create, defaultOptions),
update: angular.extend(links.annotation.update, defaultOptions),
delete: angular.extend(links.annotation.delete, defaultOptions),
});
return instance;
});
return instance;
}
......
'use strict';
var inject = angular.mock.inject;
var module = angular.mock.module;
var angular = require('angular');
var proxyquire = require('proxyquire');
var util = require('./util');
describe('store', function () {
var $httpBackend = null;
......@@ -10,12 +12,19 @@ describe('store', function () {
before(function () {
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();
$provide.value('settings', {apiUrl: 'http://example.com/api'});
}));
......@@ -26,7 +35,7 @@ describe('store', function () {
sandbox.restore();
});
beforeEach(inject(function ($q, _$httpBackend_, _store_) {
beforeEach(angular.mock.inject(function ($q, _$httpBackend_, _store_) {
$httpBackend = _$httpBackend_;
store = _store_;
......@@ -50,8 +59,8 @@ describe('store', function () {
}));
it('reads the operations from the backend', function () {
assert.isFunction(store.AnnotationResource, 'expected store.AnnotationResource to be a function')
assert.isFunction(store.SearchResource, 'expected store.SearchResource to be a function')
assert.isFunction(store.AnnotationResource);
assert.isFunction(store.SearchResource);
});
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