Commit a3e1cb81 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch '38394-smarter-interval' into 'master'

don't re-run smart interval callback if there is already one in progress

Closes #38394

See merge request gitlab-org/gitlab-ce!15032
parents c8eb789b 862d781d
...@@ -3,9 +3,10 @@ ...@@ -3,9 +3,10 @@
* and controllable by a public API. * and controllable by a public API.
*/ */
class SmartInterval { export default class SmartInterval {
/** /**
* @param { function } opts.callback Function to be called on each iteration (required) * @param { function } opts.callback Function that returns a promise, called on each iteration
* unless still in progress (required)
* @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
* @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
* @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
...@@ -42,13 +43,16 @@ class SmartInterval { ...@@ -42,13 +43,16 @@ class SmartInterval {
const cfg = this.cfg; const cfg = this.cfg;
const state = this.state; const state = this.state;
if (cfg.immediateExecution) { if (cfg.immediateExecution && !this.isLoading) {
cfg.immediateExecution = false; cfg.immediateExecution = false;
cfg.callback(); this.triggerCallback();
} }
state.intervalId = window.setInterval(() => { state.intervalId = window.setInterval(() => {
cfg.callback(); if (this.isLoading) {
return;
}
this.triggerCallback();
if (this.getCurrentInterval() === cfg.maxInterval) { if (this.getCurrentInterval() === cfg.maxInterval) {
return; return;
...@@ -76,7 +80,7 @@ class SmartInterval { ...@@ -76,7 +80,7 @@ class SmartInterval {
// start a timer, using the existing interval // start a timer, using the existing interval
resume() { resume() {
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped this.stopTimer(); // stop existing timer, in case timer was not previously stopped
this.start(); this.start();
} }
...@@ -104,6 +108,18 @@ class SmartInterval { ...@@ -104,6 +108,18 @@ class SmartInterval {
this.initPageUnloadHandling(); this.initPageUnloadHandling();
} }
triggerCallback() {
this.isLoading = true;
this.cfg.callback()
.then(() => {
this.isLoading = false;
})
.catch((err) => {
this.isLoading = false;
throw err;
});
}
initVisibilityChangeHandling() { initVisibilityChangeHandling() {
// cancel interval when tab no longer shown (prevents cached pages from polling) // cancel interval when tab no longer shown (prevents cached pages from polling)
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
...@@ -154,4 +170,3 @@ class SmartInterval { ...@@ -154,4 +170,3 @@ class SmartInterval {
} }
} }
window.gl.SmartInterval = SmartInterval;
import SmartInterval from '~/smart_interval';
import Flash from '../flash'; import Flash from '../flash';
import { import {
WidgetHeader, WidgetHeader,
...@@ -81,7 +82,7 @@ export default { ...@@ -81,7 +82,7 @@ export default {
return new MRWidgetService(endpoints); return new MRWidgetService(endpoints);
}, },
checkStatus(cb) { checkStatus(cb) {
this.service.checkStatus() return this.service.checkStatus()
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then((res) => {
this.handleNotification(res); this.handleNotification(res);
...@@ -97,7 +98,7 @@ export default { ...@@ -97,7 +98,7 @@ export default {
}); });
}, },
initPolling() { initPolling() {
this.pollingInterval = new gl.SmartInterval({ this.pollingInterval = new SmartInterval({
callback: this.checkStatus, callback: this.checkStatus,
startingInterval: 10000, startingInterval: 10000,
maxInterval: 30000, maxInterval: 30000,
...@@ -106,7 +107,7 @@ export default { ...@@ -106,7 +107,7 @@ export default {
}); });
}, },
initDeploymentsPolling() { initDeploymentsPolling() {
this.deploymentsInterval = new gl.SmartInterval({ this.deploymentsInterval = new SmartInterval({
callback: this.fetchDeployments, callback: this.fetchDeployments,
startingInterval: 30000, startingInterval: 30000,
maxInterval: 120000, maxInterval: 120000,
...@@ -121,7 +122,7 @@ export default { ...@@ -121,7 +122,7 @@ export default {
} }
}, },
fetchDeployments() { fetchDeployments() {
this.service.fetchDeployments() return this.service.fetchDeployments()
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then((res) => {
if (res.length) { if (res.length) {
......
---
title: Update Merge Request polling so there is only one request at a time
merge_request: 15032
author:
type: fixed
import '~/smart_interval'; import SmartInterval from '~/smart_interval';
(() => { describe('SmartInterval', function () {
const DEFAULT_MAX_INTERVAL = 100; const DEFAULT_MAX_INTERVAL = 100;
const DEFAULT_STARTING_INTERVAL = 5; const DEFAULT_STARTING_INTERVAL = 5;
const DEFAULT_SHORT_TIMEOUT = 75; const DEFAULT_SHORT_TIMEOUT = 75;
...@@ -9,7 +9,7 @@ import '~/smart_interval'; ...@@ -9,7 +9,7 @@ import '~/smart_interval';
function createDefaultSmartInterval(config) { function createDefaultSmartInterval(config) {
const defaultParams = { const defaultParams = {
callback: () => {}, callback: () => Promise.resolve(),
startingInterval: DEFAULT_STARTING_INTERVAL, startingInterval: DEFAULT_STARTING_INTERVAL,
maxInterval: DEFAULT_MAX_INTERVAL, maxInterval: DEFAULT_MAX_INTERVAL,
incrementByFactorOf: DEFAULT_INCREMENT_FACTOR, incrementByFactorOf: DEFAULT_INCREMENT_FACTOR,
...@@ -22,10 +22,9 @@ import '~/smart_interval'; ...@@ -22,10 +22,9 @@ import '~/smart_interval';
_.extend(defaultParams, config); _.extend(defaultParams, config);
} }
return new gl.SmartInterval(defaultParams); return new SmartInterval(defaultParams);
} }
describe('SmartInterval', function () {
describe('Increment Interval', function () { describe('Increment Interval', function () {
beforeEach(function () { beforeEach(function () {
this.smartInterval = createDefaultSmartInterval(); this.smartInterval = createDefaultSmartInterval();
...@@ -58,6 +57,21 @@ import '~/smart_interval'; ...@@ -58,6 +57,21 @@ import '~/smart_interval';
done(); done();
}, DEFAULT_LONG_TIMEOUT); }, DEFAULT_LONG_TIMEOUT);
}); });
it('does not increment while waiting for callback', function () {
jasmine.clock().install();
const smartInterval = createDefaultSmartInterval({
callback: () => new Promise($.noop),
});
jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT);
const oneInterval = smartInterval.cfg.startingInterval * DEFAULT_INCREMENT_FACTOR;
expect(smartInterval.getCurrentInterval()).toEqual(oneInterval);
jasmine.clock().uninstall();
});
}); });
describe('Public methods', function () { describe('Public methods', function () {
...@@ -175,5 +189,4 @@ import '~/smart_interval'; ...@@ -175,5 +189,4 @@ import '~/smart_interval';
expect(interval.cfg.immediateExecution).toBeFalsy(); expect(interval.cfg.immediateExecution).toBeFalsy();
}); });
}); });
}); });
})(window.gl || (window.gl = {}));
...@@ -121,24 +121,28 @@ describe('mrWidgetOptions', () => { ...@@ -121,24 +121,28 @@ describe('mrWidgetOptions', () => {
describe('initPolling', () => { describe('initPolling', () => {
it('should call SmartInterval', () => { it('should call SmartInterval', () => {
spyOn(gl, 'SmartInterval').and.returnValue({ spyOn(vm, 'checkStatus').and.returnValue(Promise.resolve());
resume() {}, jasmine.clock().install();
stopTimer() {},
});
vm.initPolling(); vm.initPolling();
expect(vm.checkStatus).not.toHaveBeenCalled();
jasmine.clock().tick(10000);
expect(vm.pollingInterval).toBeDefined(); expect(vm.pollingInterval).toBeDefined();
expect(gl.SmartInterval).toHaveBeenCalled(); expect(vm.checkStatus).toHaveBeenCalled();
jasmine.clock().uninstall();
}); });
}); });
describe('initDeploymentsPolling', () => { describe('initDeploymentsPolling', () => {
it('should call SmartInterval', () => { it('should call SmartInterval', () => {
spyOn(gl, 'SmartInterval'); spyOn(vm, 'fetchDeployments').and.returnValue(Promise.resolve());
vm.initDeploymentsPolling(); vm.initDeploymentsPolling();
expect(vm.deploymentsInterval).toBeDefined(); expect(vm.deploymentsInterval).toBeDefined();
expect(gl.SmartInterval).toHaveBeenCalled(); expect(vm.fetchDeployments).toHaveBeenCalled();
}); });
}); });
......
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