diff --git a/app/assets/javascripts/updateContent.js b/app/assets/javascripts/updateContent.js index 6297e3854..f519a0f63 100644 --- a/app/assets/javascripts/updateContent.js +++ b/app/assets/javascripts/updateContent.js @@ -3,6 +3,12 @@ var queues = {}; var dd = new global.diffDOM(); + var interval = 0; + + var calculateBackoff = responseTime => parseInt(Math.max( + (250 * Math.sqrt(responseTime)) - 1000, + 1000 + )); var getRenderer = $component => response => dd.apply( $component.get(0), @@ -19,7 +25,9 @@ var clearQueue = queue => (queue.length = 0); - var poll = function(renderer, resource, queue, interval, form) { + var poll = function(renderer, resource, queue, form) { + + let startTime = Date.now(); if (document.visibilityState !== "hidden" && queue.push(renderer) === 1) $.ajax( resource, @@ -33,6 +41,7 @@ if (response.stop === 1) { poll = function(){}; } + interval = calculateBackoff(Date.now() - startTime); } ).fail( () => poll = function(){} @@ -49,10 +58,11 @@ getRenderer($(component)), $(component).data('resource'), getQueue($(component).data('resource')), - ($(component).data('interval-seconds') || 1.5) * 1000, $(component).data('form') ); }; + global.GOVUK.Modules.UpdateContent.calculateBackoff = calculateBackoff; + })(window); diff --git a/app/templates/components/ajax-block.html b/app/templates/components/ajax-block.html index c0aa403d8..3638e13d3 100644 --- a/app/templates/components/ajax-block.html +++ b/app/templates/components/ajax-block.html @@ -1,10 +1,9 @@ -{% macro ajax_block(partials, url, key, interval=5, finished=False, form='') %} +{% macro ajax_block(partials, url, key, finished=False, form='') %} {% if not finished %}
diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index e7332e9f5..fa8405690 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -19,25 +19,25 @@ {% include 'views/dashboard/write-first-messages.html' %} {% endif %} - {{ ajax_block(partials, updates_url, 'upcoming', interval=20) }} + {{ ajax_block(partials, updates_url, 'upcoming') }}

In the last 7 days

- {{ ajax_block(partials, updates_url, 'inbox', interval=20) }} + {{ ajax_block(partials, updates_url, 'inbox') }} - {{ ajax_block(partials, updates_url, 'totals', interval=20) }} + {{ ajax_block(partials, updates_url, 'totals') }} {{ show_more( url_for('.monthly', service_id=current_service.id), 'See messages sent per month' ) }} - {{ ajax_block(partials, updates_url, 'template-statistics', interval=20) }} + {{ ajax_block(partials, updates_url, 'template-statistics') }} {% if current_user.has_permissions('manage_service') %}

This year

- {{ ajax_block(partials, updates_url, 'usage', interval=20) }} + {{ ajax_block(partials, updates_url, 'usage') }} {{ show_more( url_for(".usage", service_id=current_service['id']), 'See usage' diff --git a/app/templates/views/notifications.html b/app/templates/views/notifications.html index d8bfa4bdd..7c9bbabc0 100644 --- a/app/templates/views/notifications.html +++ b/app/templates/views/notifications.html @@ -31,8 +31,7 @@ {{ ajax_block( partials, url_for('.get_notifications_as_json', service_id=current_service.id, message_type=message_type, status=status), - 'counts', - interval=20 + 'counts' ) }} {% call form_wrapper( @@ -83,8 +82,7 @@ partials, url_for('.get_notifications_as_json', service_id=current_service.id, message_type=message_type, status=status, page=page), 'notifications', - form='search-form', - interval=20 + form='search-form' ) }} {% endblock %} diff --git a/app/templates/views/notifications/notification.html b/app/templates/views/notifications/notification.html index 63bf4ceed..288cb7ee4 100644 --- a/app/templates/views/notifications/notification.html +++ b/app/templates/views/notifications/notification.html @@ -96,10 +96,10 @@
{% elif template.template_type == 'email' %}
- {{ ajax_block(partials, updates_url, 'status', interval=2, finished=finished) }} + {{ ajax_block(partials, updates_url, 'status', finished=finished) }}
{% elif template.template_type == 'sms' %} - {{ ajax_block(partials, updates_url, 'status', interval=2, finished=finished) }} + {{ ajax_block(partials, updates_url, 'status', finished=finished) }} {% endif %} {% if current_user.has_permissions('send_messages') and current_user.has_permissions('view_activity') and template.template_type == 'sms' and can_receive_inbound %} diff --git a/package.json b/package.json index c9005ed6a..866f2d3c5 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,8 @@ "gulp-prettyerror": "1.2.1", "gulp-sass-lint": "1.4.0", "jest": "24.7.1", + "jest-date-mock": "^1.0.8", + "jest-each": "^25.3.0", "jshint": "2.10.2", "jshint-stylish": "2.2.1", "rollup-plugin-commonjs": "10.1.0", diff --git a/tests/javascripts/updateContent.test.js b/tests/javascripts/updateContent.test.js index b946480ec..bd1c92abb 100644 --- a/tests/javascripts/updateContent.test.js +++ b/tests/javascripts/updateContent.test.js @@ -1,3 +1,6 @@ +const each = require('jest-each').default; +const jestDateMock = require('jest-date-mock'); + const helpers = require('./support/helpers.js'); const serviceNumber = '6658542f-0cad-491f-bec8-ab8457700ead'; @@ -18,6 +21,8 @@ beforeAll(() => { // set up the object returned from $.ajax so it responds with whatever responseObj is set to jqueryAJAXReturnObj = { done: callback => { + // The server takes 1 second to respond + jestDateMock.advanceBy(1000); callback(responseObj); return jqueryAJAXReturnObj; }, @@ -100,7 +105,7 @@ describe('Update content', () => { jest.clearAllTimers(); }); - + test("It should make requests to the URL specified in the data-resource attribute", () => { // start the module @@ -136,24 +141,6 @@ describe('Update content', () => { }); - test("If an interval between requests is specified, using the data-interval-seconds attribute, requests should happen at that frequency", () => { - - document.querySelector('[data-module=update-content]').setAttribute('data-interval-seconds', '0.5'); - - // start the module - window.GOVUK.modules.start(); - - expect($.ajax).toHaveBeenCalledTimes(1); - - // units are milliseconds - jest.advanceTimersByTime(500); - jest.advanceTimersByTime(500); - jest.advanceTimersByTime(500); - - expect($.ajax).toHaveBeenCalledTimes(4); - - }); - describe("By default", () => { beforeEach(() => { @@ -175,17 +162,36 @@ describe('Update content', () => { }); - test("It should request updates every 1.5 seconds", () => { + test("It should request updates with a dynamic interval", () => { + // First call happens straight away expect($.ajax).toHaveBeenCalledTimes(1); - // units are milliseconds - jest.advanceTimersByTime(1500); + // It took the server 1000ms to respond to the first call so we + // will back off – the next call shouldn’t happen in the next 6904ms + jest.advanceTimersByTime(6904); + expect($.ajax).toHaveBeenCalledTimes(1); + // But it should happen after 6905ms + jest.advanceTimersByTime(1); expect($.ajax).toHaveBeenCalledTimes(2); }); + each([ + [1000, 0], + [1500, 100], + [4590, 500], + [6905, 1000], + [24000, 10000], + ]).test('It calculates a delay of %dms if the API responds in %dms', (waitTime, responseTime) => { + expect( + window.GOVUK.Modules.UpdateContent.calculateBackoff(responseTime) + ).toBe( + waitTime + ); + }); + }); describe("If a form is used as a source for data, referenced in the data-form attribute", () => {