gitlab-org--gitlab-foss/app/assets/javascripts/lib/utils/poll.js

151 lines
3.9 KiB
JavaScript

import { normalizeHeaders } from './common_utils';
import httpStatusCodes, { successCodes } from './http_status';
/**
* Polling utility for handling realtime updates.
* Requirements: Promise based HTTP client
*
* Service for promise based http client and method need to be provided as props
*
* @example
* new Poll({
* resource: resource,
* method: 'name',
* data: {page: 1, scope: 'all'}, // optional
* successCallback: () => {},
* errorCallback: () => {},
* notificationCallback: () => {}, // optional
* }).makeRequest();
*
* Usage in pipelines table with visibility lib:
*
* const poll = new Poll({
* resource: this.service,
* method: 'getPipelines',
* data: { page: pageNumber, scope },
* successCallback: this.successCallback,
* errorCallback: this.errorCallback,
* notificationCallback: this.updateLoading,
* });
*
* if (!Visibility.hidden()) {
* poll.makeRequest();
* }
*
* Visibility.change(() => {
* if (!Visibility.hidden()) {
* poll.restart();
* } else {
* poll.stop();
* }
* });
*
* 1. Checks for response and headers before start polling
* 2. Interval is provided by `Poll-Interval` header.
* 3. If `Poll-Interval` is -1, we stop polling
* 4. If HTTP response is 200, we poll.
* 5. If HTTP response is different from 200, we stop polling.
*
* @example
* // With initial delay (for, for example, reducing unnecessary requests)
*
* const poll = new Poll({
* resource: this.service,
* method: 'fetchNotes',
* successCallback: () => {},
* errorCallback: () => {},
* });
*
* // Performs the first request in 2.5s and then uses the `Poll-Interval` header.
* poll.makeDelayedRequest(2500);
*
*/
export default class Poll {
constructor(options = {}) {
this.options = options;
this.options.data = options.data || {};
this.options.notificationCallback =
options.notificationCallback || function notificationCallback() {};
this.intervalHeader = 'POLL-INTERVAL';
this.timeoutID = null;
this.canPoll = true;
}
checkConditions(response) {
const headers = normalizeHeaders(response.headers);
const pollInterval = parseInt(headers[this.intervalHeader], 10);
if (pollInterval > 0 && successCodes.indexOf(response.status) !== -1 && this.canPoll) {
if (this.timeoutID) {
clearTimeout(this.timeoutID);
}
this.timeoutID = setTimeout(() => {
this.makeRequest();
}, pollInterval);
}
this.options.successCallback(response);
}
makeDelayedRequest(delay = 0) {
// So we don't make our specs artificially slower
this.timeoutID = setTimeout(
() => this.makeRequest(),
process.env.NODE_ENV !== 'test' ? delay : 1,
);
}
makeRequest() {
const { resource, method, data, errorCallback, notificationCallback } = this.options;
// It's called everytime a new request is made. Useful to update the status.
notificationCallback(true);
return resource[method](data)
.then((response) => {
this.checkConditions(response);
notificationCallback(false);
})
.catch((error) => {
notificationCallback(false);
if (error.status === httpStatusCodes.ABORTED) {
return;
}
errorCallback(error);
});
}
/**
* Stops the polling recursive chain
* and guarantees if the timeout is already running it won't make another request by
* cancelling the previously established timeout.
*/
stop() {
this.canPoll = false;
clearTimeout(this.timeoutID);
}
/**
* Enables polling after it has been stopped
*/
enable(options) {
if (options && options.data) {
this.options.data = options.data;
}
this.canPoll = true;
if (options && options.response) {
this.checkConditions(options.response);
}
}
/**
* Restarts polling after it has been stopped and makes a request
*/
restart(options) {
this.enable(options);
this.makeRequest();
}
}