mirror of https://github.com/grafana/grafana
feat(datasource): cancel in flight data source requests, refeatoring #5321
parent
6d3521c240
commit
81e9aa4de4
@ -1,175 +0,0 @@ |
||||
define([ |
||||
'angular', |
||||
'lodash', |
||||
'../core_module', |
||||
'app/core/config', |
||||
], |
||||
function (angular, _, coreModule, config) { |
||||
'use strict'; |
||||
|
||||
coreModule.default.service('backendSrv', function($http, alertSrv, $timeout, $q) { |
||||
var self = this; |
||||
|
||||
this.get = function(url, params) { |
||||
return this.request({ method: 'GET', url: url, params: params }); |
||||
}; |
||||
|
||||
this.delete = function(url) { |
||||
return this.request({ method: 'DELETE', url: url }); |
||||
}; |
||||
|
||||
this.post = function(url, data) { |
||||
return this.request({ method: 'POST', url: url, data: data }); |
||||
}; |
||||
|
||||
this.patch = function(url, data) { |
||||
return this.request({ method: 'PATCH', url: url, data: data }); |
||||
}; |
||||
|
||||
this.put = function(url, data) { |
||||
return this.request({ method: 'PUT', url: url, data: data }); |
||||
}; |
||||
|
||||
this._handleError = function(err) { |
||||
return function() { |
||||
if (err.isHandled) { |
||||
return; |
||||
} |
||||
|
||||
var data = err.data || { message: 'Unexpected error' }; |
||||
if (_.isString(data)) { |
||||
data = { message: data }; |
||||
} |
||||
|
||||
if (err.status === 422) { |
||||
alertSrv.set("Validation failed", data.message, "warning", 4000); |
||||
throw data; |
||||
} |
||||
|
||||
data.severity = 'error'; |
||||
|
||||
if (err.status < 500) { |
||||
data.severity = "warning"; |
||||
} |
||||
|
||||
if (data.message) { |
||||
alertSrv.set("Problem!", data.message, data.severity, 10000); |
||||
} |
||||
|
||||
throw data; |
||||
}; |
||||
}; |
||||
|
||||
this.request = function(options) { |
||||
options.retry = options.retry || 0; |
||||
var requestIsLocal = options.url.indexOf('/') === 0; |
||||
var firstAttempt = options.retry === 0; |
||||
|
||||
if (requestIsLocal && !options.hasSubUrl) { |
||||
options.url = config.appSubUrl + options.url; |
||||
options.hasSubUrl = true; |
||||
} |
||||
|
||||
return $http(options).then(function(results) { |
||||
if (options.method !== 'GET') { |
||||
if (results && results.data.message) { |
||||
alertSrv.set(results.data.message, '', 'success', 3000); |
||||
} |
||||
} |
||||
return results.data; |
||||
}, function(err) { |
||||
// handle unauthorized
|
||||
if (err.status === 401 && firstAttempt) { |
||||
return self.loginPing().then(function() { |
||||
options.retry = 1; |
||||
return self.request(options); |
||||
}); |
||||
} |
||||
|
||||
$timeout(self._handleError(err), 50); |
||||
throw err; |
||||
}); |
||||
}; |
||||
|
||||
var datasourceInFlightRequests = {}; |
||||
var HTTP_REQUEST_ABORTED = -1; |
||||
this.datasourceRequest = function(options) { |
||||
options.retry = options.retry || 0; |
||||
|
||||
// A requestID is provided by the datasource as a unique identifier for a
|
||||
// particular query. If the requestID exists, the promise it is keyed to
|
||||
// is canceled, canceling the previous datasource request if it is still
|
||||
// in-flight.
|
||||
var canceler; |
||||
if (options.requestID) { |
||||
if (canceler = datasourceInFlightRequests[options.requestID]) { |
||||
canceler.resolve(); |
||||
} |
||||
canceler = $q.defer(); |
||||
options.timeout = canceler.promise; |
||||
datasourceInFlightRequests[options.requestID] = canceler; |
||||
} |
||||
|
||||
var requestIsLocal = options.url.indexOf('/') === 0; |
||||
var firstAttempt = options.retry === 0; |
||||
|
||||
if (requestIsLocal && options.headers && options.headers.Authorization) { |
||||
options.headers['X-DS-Authorization'] = options.headers.Authorization; |
||||
delete options.headers.Authorization; |
||||
} |
||||
|
||||
return $http(options).then(null, function(err) { |
||||
if (err.status === HTTP_REQUEST_ABORTED) { |
||||
// TODO: Hitting refresh before the original request returns cancels
|
||||
// the "loading" animation on the panes, but it should continue to be
|
||||
// visible.
|
||||
err.statusText = "request aborted"; |
||||
return err; |
||||
} |
||||
|
||||
// handle unauthorized for backend requests
|
||||
if (requestIsLocal && firstAttempt && err.status === 401) { |
||||
return self.loginPing().then(function() { |
||||
options.retry = 1; |
||||
if (canceler) { |
||||
canceler.resolve(); |
||||
} |
||||
return self.datasourceRequest(options); |
||||
}); |
||||
} |
||||
|
||||
//populate error obj on Internal Error
|
||||
if (_.isString(err.data) && err.status === 500) { |
||||
err.data = { |
||||
error: err.statusText |
||||
}; |
||||
} |
||||
|
||||
// for Prometheus
|
||||
if (!err.data.message && _.isString(err.data.error)) { |
||||
err.data.message = err.data.error; |
||||
} |
||||
|
||||
throw err; |
||||
}); |
||||
}; |
||||
|
||||
this.loginPing = function() { |
||||
return this.request({url: '/api/login/ping', method: 'GET', retry: 1 }); |
||||
}; |
||||
|
||||
this.search = function(query) { |
||||
return this.get('/api/search', query); |
||||
}; |
||||
|
||||
this.getDashboard = function(type, slug) { |
||||
return this.get('/api/dashboards/' + type + '/' + slug); |
||||
}; |
||||
|
||||
this.saveDashboard = function(dash, options) { |
||||
options = (options || {}); |
||||
return this.post('/api/dashboards/db/', {dashboard: dash, overwrite: options.overwrite === true}); |
||||
}; |
||||
|
||||
}); |
||||
}); |
||||
@ -0,0 +1,177 @@ |
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular'; |
||||
import _ from 'lodash'; |
||||
import config from 'app/core/config'; |
||||
import coreModule from 'app/core/core_module'; |
||||
|
||||
export class BackendSrv { |
||||
inFlightRequests = {}; |
||||
HTTP_REQUEST_CANCELLED = -1; |
||||
|
||||
/** @ngInject */ |
||||
constructor(private $http, private alertSrv, private $rootScope, private $q, private $timeout) { |
||||
} |
||||
|
||||
get(url, params?) { |
||||
return this.request({ method: 'GET', url: url, params: params }); |
||||
} |
||||
|
||||
delete(url) { |
||||
return this.request({ method: 'DELETE', url: url }); |
||||
} |
||||
|
||||
post(url, data) { |
||||
return this.request({ method: 'POST', url: url, data: data }); |
||||
}; |
||||
|
||||
patch(url, data) { |
||||
return this.request({ method: 'PATCH', url: url, data: data }); |
||||
} |
||||
|
||||
put(url, data) { |
||||
return this.request({ method: 'PUT', url: url, data: data }); |
||||
} |
||||
|
||||
requestErrorHandler(err) { |
||||
if (err.isHandled) { |
||||
return; |
||||
} |
||||
|
||||
var data = err.data || { message: 'Unexpected error' }; |
||||
if (_.isString(data)) { |
||||
data = { message: data }; |
||||
} |
||||
|
||||
if (err.status === 422) { |
||||
this.alertSrv.set("Validation failed", data.message, "warning", 4000); |
||||
throw data; |
||||
} |
||||
|
||||
data.severity = 'error'; |
||||
|
||||
if (err.status < 500) { |
||||
data.severity = "warning"; |
||||
} |
||||
|
||||
if (data.message) { |
||||
this.alertSrv.set("Problem!", data.message, data.severity, 10000); |
||||
} |
||||
|
||||
throw data; |
||||
} |
||||
|
||||
request(options) { |
||||
options.retry = options.retry || 0; |
||||
var requestIsLocal = options.url.indexOf('/') === 0; |
||||
var firstAttempt = options.retry === 0; |
||||
|
||||
if (requestIsLocal && !options.hasSubUrl) { |
||||
options.url = config.appSubUrl + options.url; |
||||
options.hasSubUrl = true; |
||||
} |
||||
|
||||
return this.$http(options).then(results => { |
||||
if (options.method !== 'GET') { |
||||
if (results && results.data.message) { |
||||
this.alertSrv.set(results.data.message, '', 'success', 3000); |
||||
} |
||||
} |
||||
return results.data; |
||||
}, err => { |
||||
// handle unauthorized
|
||||
if (err.status === 401 && firstAttempt) { |
||||
return this.loginPing().then(() => { |
||||
options.retry = 1; |
||||
return this.request(options); |
||||
}); |
||||
} |
||||
|
||||
this.$timeout(this.requestErrorHandler.bind(this), 50); |
||||
throw err; |
||||
}); |
||||
}; |
||||
|
||||
datasourceRequest(options) { |
||||
options.retry = options.retry || 0; |
||||
|
||||
// A requestID is provided by the datasource as a unique identifier for a
|
||||
// particular query. If the requestID exists, the promise it is keyed to
|
||||
// is canceled, canceling the previous datasource request if it is still
|
||||
// in-flight.
|
||||
var canceler; |
||||
if (options.requestId) { |
||||
canceler = this.inFlightRequests[options.requestId]; |
||||
if (canceler) { |
||||
canceler.resolve(); |
||||
} |
||||
// create new canceler
|
||||
canceler = this.$q.defer(); |
||||
options.timeout = canceler.promise; |
||||
this.inFlightRequests[options.requestId] = canceler; |
||||
} |
||||
|
||||
var requestIsLocal = options.url.indexOf('/') === 0; |
||||
var firstAttempt = options.retry === 0; |
||||
|
||||
if (requestIsLocal && options.headers && options.headers.Authorization) { |
||||
options.headers['X-DS-Authorization'] = options.headers.Authorization; |
||||
delete options.headers.Authorization; |
||||
} |
||||
|
||||
return this.$http(options).catch(err => { |
||||
if (err.status === this.HTTP_REQUEST_CANCELLED) { |
||||
throw {err, cancelled: true}; |
||||
} |
||||
|
||||
// handle unauthorized for backend requests
|
||||
if (requestIsLocal && firstAttempt && err.status === 401) { |
||||
return this.loginPing().then(() => { |
||||
options.retry = 1; |
||||
if (canceler) { |
||||
canceler.resolve(); |
||||
} |
||||
return this.datasourceRequest(options); |
||||
}); |
||||
} |
||||
|
||||
//populate error obj on Internal Error
|
||||
if (_.isString(err.data) && err.status === 500) { |
||||
err.data = { |
||||
error: err.statusText |
||||
}; |
||||
} |
||||
|
||||
// for Prometheus
|
||||
if (!err.data.message && _.isString(err.data.error)) { |
||||
err.data.message = err.data.error; |
||||
} |
||||
|
||||
throw err; |
||||
}).finally(() => { |
||||
// clean up
|
||||
if (options.requestId) { |
||||
delete this.inFlightRequests[options.requestId]; |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
loginPing() { |
||||
return this.request({url: '/api/login/ping', method: 'GET', retry: 1 }); |
||||
} |
||||
|
||||
search(query) { |
||||
return this.get('/api/search', query); |
||||
} |
||||
|
||||
getDashboard(type, slug) { |
||||
return this.get('/api/dashboards/' + type + '/' + slug); |
||||
} |
||||
|
||||
saveDashboard(dash, options) { |
||||
options = (options || {}); |
||||
return this.post('/api/dashboards/db/', {dashboard: dash, overwrite: options.overwrite === true}); |
||||
} |
||||
} |
||||
|
||||
coreModule.service('backendSrv', BackendSrv); |
||||
Loading…
Reference in new issue