EVOLUTION-MANAGER
Edit File: backend_srv.ts
import _ from 'lodash'; import angular from 'angular'; import coreModule from 'app/core/core_module'; import appEvents from 'app/core/app_events'; import config from 'app/core/config'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardSearchHit } from 'app/types/search'; import { ContextSrv } from './context_srv'; import { FolderInfo, DashboardDTO, CoreEvents } from 'app/types'; import { BackendSrv as BackendService, getBackendSrv as getBackendService, BackendSrvRequest } from '@grafana/runtime'; import { AppEvents } from '@grafana/data'; export interface DatasourceRequestOptions { retry?: number; method?: string; requestId?: string; timeout?: angular.IPromise<any>; url?: string; headers?: { [key: string]: any }; silent?: boolean; data?: { [key: string]: any }; } export class BackendSrv implements BackendService { private inFlightRequests: { [key: string]: Array<angular.IDeferred<any>> } = {}; private HTTP_REQUEST_CANCELED = -1; private noBackendCache: boolean; /** @ngInject */ constructor( private $http: any, private $q: angular.IQService, private $timeout: angular.ITimeoutService, private contextSrv: ContextSrv ) {} get(url: string, params?: any) { return this.request({ method: 'GET', url, params }); } delete(url: string) { return this.request({ method: 'DELETE', url }); } post(url: string, data?: any) { return this.request({ method: 'POST', url, data }); } patch(url: string, data: any) { return this.request({ method: 'PATCH', url, data }); } put(url: string, data: any) { return this.request({ method: 'PUT', url, data }); } withNoBackendCache(callback: any) { this.noBackendCache = true; return callback().finally(() => { this.noBackendCache = false; }); } requestErrorHandler(err: any) { if (err.isHandled) { return; } let data = err.data || { message: 'Unexpected error' }; if (_.isString(data)) { data = { message: data }; } if (err.status === 422) { appEvents.emit(AppEvents.alertWarning, ['Validation failed', data.message]); throw data; } if (data.message) { let description = ''; let message = data.message; if (message.length > 80) { description = message; message = 'Error'; } appEvents.emit(err.status < 500 ? AppEvents.alertWarning : AppEvents.alertError, [message, description]); } throw data; } request(options: BackendSrvRequest) { options.retry = options.retry || 0; const requestIsLocal = !options.url.match(/^http/); const firstAttempt = options.retry === 0; if (requestIsLocal) { if (this.contextSrv.user && this.contextSrv.user.orgId) { options.headers = options.headers || {}; options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId; } if (options.url.indexOf('/') === 0) { options.url = options.url.substring(1); } } return this.$http(options).then( (results: any) => { if (options.method !== 'GET') { if (results && results.data.message) { if (options.showSuccessAlert !== false) { appEvents.emit(AppEvents.alertSuccess, [results.data.message]); } } } return results.data; }, (err: any) => { // handle unauthorized if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) { return this.loginPing() .then(() => { options.retry = 1; return this.request(options); }) .catch((err: any) => { if (err.status === 401) { window.location.href = config.appSubUrl + '/logout'; throw err; } }); } this.$timeout(this.requestErrorHandler.bind(this, err), 50); throw err; } ); } addCanceler(requestId: string, canceler: angular.IDeferred<any>) { if (requestId in this.inFlightRequests) { this.inFlightRequests[requestId].push(canceler); } else { this.inFlightRequests[requestId] = [canceler]; } } resolveCancelerIfExists(requestId: string) { const cancelers = this.inFlightRequests[requestId]; if (!_.isUndefined(cancelers) && cancelers.length) { cancelers[0].resolve(); } } datasourceRequest(options: BackendSrvRequest) { let canceler: angular.IDeferred<any> = null; 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. const requestId = options.requestId; if (requestId) { this.resolveCancelerIfExists(requestId); // create new canceler canceler = this.$q.defer(); options.timeout = canceler.promise; this.addCanceler(requestId, canceler); } const requestIsLocal = !options.url.match(/^http/); const firstAttempt = options.retry === 0; if (requestIsLocal) { if (this.contextSrv.user && this.contextSrv.user.orgId) { options.headers = options.headers || {}; options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId; } if (options.url.indexOf('/') === 0) { options.url = options.url.substring(1); } if (options.headers && options.headers.Authorization) { options.headers['X-DS-Authorization'] = options.headers.Authorization; delete options.headers.Authorization; } if (this.noBackendCache) { options.headers['X-Grafana-NoCache'] = 'true'; } } return this.$http(options) .then((response: any) => { if (!options.silent) { appEvents.emit(CoreEvents.dsRequestResponse, response); } return response; }) .catch((err: any) => { if (err.status === this.HTTP_REQUEST_CANCELED) { 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); }) .catch((err: any) => { if (err.status === 401) { window.location.href = config.appSubUrl + '/logout'; throw err; } }); } // populate error obj on Internal Error if (_.isString(err.data) && err.status === 500) { err.data = { error: err.statusText, response: err.data, }; } // for Prometheus if (err.data && !err.data.message && _.isString(err.data.error)) { err.data.message = err.data.error; } if (!options.silent) { appEvents.emit(CoreEvents.dsRequestError, err); } throw err; }) .finally(() => { // clean up if (options.requestId) { this.inFlightRequests[options.requestId].shift(); } }); } loginPing() { return this.request({ url: '/api/login/ping', method: 'GET', retry: 1 }); } search(query: any): Promise<DashboardSearchHit[]> { return this.get('/api/search', query); } getDashboardBySlug(slug: string) { return this.get(`/api/dashboards/db/${slug}`); } getDashboardByUid(uid: string) { return this.get(`/api/dashboards/uid/${uid}`); } getFolderByUid(uid: string) { return this.get(`/api/folders/${uid}`); } saveDashboard( dash: DashboardModel, { message = '', folderId, overwrite = false }: { message?: string; folderId?: number; overwrite?: boolean } = {} ) { return this.post('/api/dashboards/db/', { dashboard: dash, folderId, overwrite, message, }); } createFolder(payload: any) { return this.post('/api/folders', payload); } deleteFolder(uid: string, showSuccessAlert: boolean) { return this.request({ method: 'DELETE', url: `/api/folders/${uid}`, showSuccessAlert: showSuccessAlert === true }); } deleteDashboard(uid: string, showSuccessAlert: boolean) { return this.request({ method: 'DELETE', url: `/api/dashboards/uid/${uid}`, showSuccessAlert: showSuccessAlert === true, }); } deleteFoldersAndDashboards(folderUids: string[], dashboardUids: string[]) { const tasks = []; for (const folderUid of folderUids) { tasks.push(this.createTask(this.deleteFolder.bind(this), true, folderUid, true)); } for (const dashboardUid of dashboardUids) { tasks.push(this.createTask(this.deleteDashboard.bind(this), true, dashboardUid, true)); } return this.executeInOrder(tasks, []); } moveDashboards(dashboardUids: string[], toFolder: FolderInfo) { const tasks = []; for (const uid of dashboardUids) { tasks.push(this.createTask(this.moveDashboard.bind(this), true, uid, toFolder)); } return this.executeInOrder(tasks, []).then((result: any) => { return { totalCount: result.length, successCount: _.filter(result, { succeeded: true }).length, alreadyInFolderCount: _.filter(result, { alreadyInFolder: true }).length, }; }); } private moveDashboard(uid: string, toFolder: FolderInfo) { const deferred = this.$q.defer(); this.getDashboardByUid(uid).then((fullDash: DashboardDTO) => { const model = new DashboardModel(fullDash.dashboard, fullDash.meta); if ((!fullDash.meta.folderId && toFolder.id === 0) || fullDash.meta.folderId === toFolder.id) { deferred.resolve({ alreadyInFolder: true }); return; } const clone = model.getSaveModelClone(); const options = { folderId: toFolder.id, overwrite: false, }; this.saveDashboard(clone, options) .then(() => { deferred.resolve({ succeeded: true }); }) .catch((err: any) => { if (err.data && err.data.status === 'plugin-dashboard') { err.isHandled = true; options.overwrite = true; this.saveDashboard(clone, options) .then(() => { deferred.resolve({ succeeded: true }); }) .catch((err: any) => { deferred.resolve({ succeeded: false }); }); } else { deferred.resolve({ succeeded: false }); } }); }); return deferred.promise; } private createTask(fn: Function, ignoreRejections: boolean, ...args: any[]) { return (result: any) => { return fn .apply(null, args) .then((res: any) => { return Array.prototype.concat(result, [res]); }) .catch((err: any) => { if (ignoreRejections) { return result; } throw err; }); }; } private executeInOrder(tasks: any[], initialValue: any[]) { return tasks.reduce(this.$q.when, initialValue); } } coreModule.service('backendSrv', BackendSrv); // Used for testing and things that really need BackendSrv export function getBackendSrv(): BackendSrv { return getBackendService() as BackendSrv; }