mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Remove angular dependency from backendSrv (#20999)
* Chore: Remove angular dependency from backendSrv * Refactor: Naive soultion for logging out unauthorized users * Refactor: Restructures to different streams * Refactor: Restructures datasourceRequest * Refactor: Flipped back if statement * Refactor: Extracted getFromFetchStream * Refactor: Extracts toFailureStream operation * Refactor: Fixes issue when options.params contains arrays * Refactor: Fixes broken test (but we need a lot more) * Refactor: Adds explaining comments * Refactor: Adds latest RxJs version so cancellations work * Refactor: Cleans up the takeUntil code * Refactor: Adds tests for request function * Refactor: Separates into smaller functions * Refactor: Adds last error tests * Started to changed so we require getBackendSrv from the @grafana-runtime when applicable. * Using the getBackendSrv from @grafana/runtime. * Changed so we use the getBackendSrv from the @grafana-runtime when possible. * Fixed so Server Admin -> Orgs works again. * Removed unused dependency. * Fixed digest issues on the Server Admin -> Users page. * Fix: Fixes digest problems in Playlists * Fix: Fixes digest issues in VersionHistory * Tests: Fixes broken tests * Fix: Fixes digest issues in Alerting => Notification channels * Fixed digest issues on the Intive page. * Fixed so we run digest after password reset email sent. * Fixed digest issue when trying to sign up account. * Fixed so the Server Admin -> Edit Org works with backendSrv * Fixed so Server Admin -> Users works with backend srv. * Fixed digest issues in Server Admin -> Orgs * Fix: Fixes digest issues in DashList plugin * Fixed digest issues on Server Admin -> users. * Fix: Fixes digest issues with Snapshots * Fixed digest issue when deleting a user. * Fix: Fixes digest issues with dashLink * Chore: Changes RxJs version to 6.5.4 which includes the same cancellation fix * Fix: Fixes digest issue when toggling folder in manage dashboards * Fix: Fixes bug in executeInOrder * Fix: Fixes digest issue with CreateFolderCtrl and FolderDashboardsCtrl * Fix: Fixes tslint error in test * Refactor: Changes default behaviour for emitted messages as before migration * Fix: Fixes various digest issues when saving, starring or deleting dashboards * Fix: Fixes digest issues with FolderPickerCtrl * Fixed digest issue. * Fixed digest issues. * Fixed issues with angular digest. * Removed the this.digest pattern. Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com> Co-authored-by: Marcus Andersson <systemvetaren@gmail.com>
This commit is contained in:
parent
6ff315a299
commit
cf2cc71393
@ -259,7 +259,7 @@
|
||||
"redux-thunk": "2.3.0",
|
||||
"reselect": "4.0.0",
|
||||
"rst2html": "github:thoward/rst2html#990cb89",
|
||||
"rxjs": "6.4.0",
|
||||
"rxjs": "6.5.4",
|
||||
"search-query-parser": "1.5.2",
|
||||
"slate": "0.47.8",
|
||||
"slate-plain-serializer": "0.7.10",
|
||||
|
@ -39,7 +39,7 @@
|
||||
"rollup-plugin-terser": "4.0.4",
|
||||
"rollup-plugin-typescript2": "0.19.3",
|
||||
"rollup-plugin-visualizer": "0.9.2",
|
||||
"rxjs": "6.4.0",
|
||||
"rxjs": "6.5.4",
|
||||
"sinon": "1.17.6",
|
||||
"typescript": "3.7.2"
|
||||
}
|
||||
|
@ -43,10 +43,8 @@ export interface BackendSrv {
|
||||
|
||||
let singletonInstance: BackendSrv;
|
||||
|
||||
export function setBackendSrv(instance: BackendSrv) {
|
||||
export const setBackendSrv = (instance: BackendSrv) => {
|
||||
singletonInstance = instance;
|
||||
}
|
||||
};
|
||||
|
||||
export function getBackendSrv(): BackendSrv {
|
||||
return singletonInstance;
|
||||
}
|
||||
export const getBackendSrv = (): BackendSrv => singletonInstance;
|
||||
|
@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { AsyncSelect } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { debounce } from 'lodash';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSearchHit, DashboardDTO } from 'app/types';
|
||||
|
||||
export interface Props {
|
||||
@ -33,20 +33,16 @@ export class DashboardPicker extends PureComponent<Props, State> {
|
||||
|
||||
getDashboards = (query = '') => {
|
||||
this.setState({ isLoading: true });
|
||||
return getBackendSrv()
|
||||
.search({ type: 'dash-db', query })
|
||||
.then((result: DashboardSearchHit[]) => {
|
||||
const dashboards = result.map((item: DashboardSearchHit) => {
|
||||
return {
|
||||
id: item.id,
|
||||
value: item.id,
|
||||
label: `${item.folderTitle ? item.folderTitle : 'General'}/${item.title}`,
|
||||
};
|
||||
});
|
||||
return backendSrv.search({ type: 'dash-db', query }).then((result: DashboardSearchHit[]) => {
|
||||
const dashboards = result.map((item: DashboardSearchHit) => ({
|
||||
id: item.id,
|
||||
value: item.id,
|
||||
label: `${item.folderTitle ? item.folderTitle : 'General'}/${item.title}`,
|
||||
}));
|
||||
|
||||
this.setState({ isLoading: false });
|
||||
return dashboards;
|
||||
});
|
||||
this.setState({ isLoading: false });
|
||||
return dashboards;
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { TeamPicker } from './TeamPicker';
|
||||
|
||||
jest.mock('app/core/services/backend_srv', () => ({
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getBackendSrv: () => {
|
||||
return {
|
||||
get: () => {
|
||||
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { AsyncSelect } from '@grafana/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
export interface Team {
|
||||
id: number;
|
||||
@ -35,27 +35,28 @@ export class TeamPicker extends Component<Props, State> {
|
||||
}
|
||||
|
||||
search(query?: string) {
|
||||
const backendSrv = getBackendSrv();
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
if (_.isNil(query)) {
|
||||
query = '';
|
||||
}
|
||||
|
||||
return backendSrv.get(`/api/teams/search?perpage=100&page=1&query=${query}`).then((result: any) => {
|
||||
const teams = result.teams.map((team: any) => {
|
||||
return {
|
||||
id: team.id,
|
||||
value: team.id,
|
||||
label: team.name,
|
||||
name: team.name,
|
||||
imgUrl: team.avatarUrl,
|
||||
};
|
||||
});
|
||||
return getBackendSrv()
|
||||
.get(`/api/teams/search?perpage=100&page=1&query=${query}`)
|
||||
.then((result: any) => {
|
||||
const teams = result.teams.map((team: any) => {
|
||||
return {
|
||||
id: team.id,
|
||||
value: team.id,
|
||||
label: team.name,
|
||||
name: team.name,
|
||||
imgUrl: team.avatarUrl,
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({ isLoading: false });
|
||||
return teams;
|
||||
});
|
||||
this.setState({ isLoading: false });
|
||||
return teams;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -3,14 +3,8 @@ import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { UserPicker } from './UserPicker';
|
||||
|
||||
jest.mock('app/core/services/backend_srv', () => ({
|
||||
getBackendSrv: () => {
|
||||
return {
|
||||
get: () => {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
};
|
||||
},
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getBackendSrv: () => ({ get: jest.fn().mockResolvedValue([]) }),
|
||||
}));
|
||||
|
||||
describe('UserPicker', () => {
|
||||
|
@ -7,7 +7,7 @@ import { AsyncSelect } from '@grafana/ui';
|
||||
|
||||
// Utils & Services
|
||||
import { debounce } from 'lodash';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
// Types
|
||||
import { User } from 'app/types';
|
||||
@ -36,14 +36,13 @@ export class UserPicker extends Component<Props, State> {
|
||||
}
|
||||
|
||||
search(query?: string) {
|
||||
const backendSrv = getBackendSrv();
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
if (_.isNil(query)) {
|
||||
query = '';
|
||||
}
|
||||
|
||||
return backendSrv
|
||||
return getBackendSrv()
|
||||
.get(`/api/org/users/lookup?query=${query}&limit=10`)
|
||||
.then((result: any) => {
|
||||
return result.map((user: any) => ({
|
||||
|
@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
||||
import { FormLabel, Select } from '@grafana/ui';
|
||||
|
||||
import { DashboardSearchHit, DashboardSearchHitType } from 'app/types';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
export interface Props {
|
||||
resourceUri: string;
|
||||
@ -29,7 +29,7 @@ const timezones = [
|
||||
];
|
||||
|
||||
export class SharedPreferences extends PureComponent<Props, State> {
|
||||
backendSrv = getBackendSrv();
|
||||
backendSrv = backendSrv;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@ -43,8 +43,8 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const prefs = await this.backendSrv.get(`/api/${this.props.resourceUri}/preferences`);
|
||||
const dashboards = await this.backendSrv.search({ starred: true });
|
||||
const prefs = await backendSrv.get(`/api/${this.props.resourceUri}/preferences`);
|
||||
const dashboards = await backendSrv.search({ starred: true });
|
||||
const defaultDashboardHit: DashboardSearchHit = {
|
||||
id: 0,
|
||||
title: 'Default',
|
||||
@ -62,7 +62,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
if (prefs.homeDashboardId > 0 && !dashboards.find(d => d.id === prefs.homeDashboardId)) {
|
||||
const missing = await this.backendSrv.search({ dashboardIds: [prefs.homeDashboardId] });
|
||||
const missing = await backendSrv.search({ dashboardIds: [prefs.homeDashboardId] });
|
||||
if (missing && missing.length > 0) {
|
||||
dashboards.push(missing[0]);
|
||||
}
|
||||
@ -81,7 +81,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
|
||||
const { homeDashboardId, theme, timezone } = this.state;
|
||||
|
||||
await this.backendSrv.put(`/api/${this.props.resourceUri}/preferences`, {
|
||||
await backendSrv.put(`/api/${this.props.resourceUri}/preferences`, {
|
||||
homeDashboardId,
|
||||
theme,
|
||||
timezone,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { BackendSrv } from '../services/backend_srv';
|
||||
import { backendSrv } from '../services/backend_srv';
|
||||
|
||||
const template = `
|
||||
<select class="gf-form-input" ng-model="ctrl.model" ng-options="f.value as f.text for f in ctrl.options"></select>
|
||||
@ -9,13 +9,10 @@ export class DashboardSelectorCtrl {
|
||||
model: any;
|
||||
options: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv) {}
|
||||
|
||||
$onInit() {
|
||||
this.options = [{ value: 0, text: 'Default' }];
|
||||
|
||||
return this.backendSrv.search({ starred: true }).then(res => {
|
||||
return backendSrv.search({ starred: true }).then(res => {
|
||||
res.forEach(dash => {
|
||||
this.options.push({ value: dash.id, text: dash.title });
|
||||
});
|
||||
|
@ -3,9 +3,10 @@ import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { SearchSrv } from 'app/core/services/search_srv';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { ContextSrv } from 'app/core/services/context_srv';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { promiseToDigest } from '../../utils/promiseToDigest';
|
||||
|
||||
export interface Section {
|
||||
id: number;
|
||||
@ -69,12 +70,7 @@ export class ManageDashboardsCtrl {
|
||||
hasEditPermissionInFolders: boolean;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $scope: IScope,
|
||||
private backendSrv: BackendSrv,
|
||||
private searchSrv: SearchSrv,
|
||||
private contextSrv: ContextSrv
|
||||
) {
|
||||
constructor(private $scope: IScope, private searchSrv: SearchSrv, private contextSrv: ContextSrv) {
|
||||
this.isEditor = this.contextSrv.isEditor;
|
||||
this.hasEditPermissionInFolders = this.contextSrv.hasEditPermissionInFolders;
|
||||
|
||||
@ -108,10 +104,10 @@ export class ManageDashboardsCtrl {
|
||||
.then(() => {
|
||||
if (!this.folderUid) {
|
||||
this.$scope.$digest();
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.backendSrv.getFolderByUid(this.folderUid).then((folder: any) => {
|
||||
return backendSrv.getFolderByUid(this.folderUid).then((folder: any) => {
|
||||
this.canSave = folder.canSave;
|
||||
if (!this.canSave) {
|
||||
this.hasEditPermissionInFolders = false;
|
||||
@ -216,9 +212,11 @@ export class ManageDashboardsCtrl {
|
||||
}
|
||||
|
||||
private deleteFoldersAndDashboards(folderUids: string[], dashboardUids: string[]) {
|
||||
this.backendSrv.deleteFoldersAndDashboards(folderUids, dashboardUids).then(() => {
|
||||
this.refreshList();
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
backendSrv.deleteFoldersAndDashboards(folderUids, dashboardUids).then(() => {
|
||||
this.refreshList();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getDashboardsToMove() {
|
||||
|
@ -1,9 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
import coreModule from '../../core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { promiseToDigest } from '../../utils/promiseToDigest';
|
||||
|
||||
export class SearchResultsCtrl {
|
||||
results: any;
|
||||
@ -14,7 +16,7 @@ export class SearchResultsCtrl {
|
||||
selectors: typeof e2e.pages.Dashboards.selectors;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $location: any) {
|
||||
constructor(private $location: ILocationService, private $scope: IScope) {
|
||||
this.selectors = e2e.pages.Dashboards.selectors;
|
||||
}
|
||||
|
||||
@ -24,19 +26,21 @@ export class SearchResultsCtrl {
|
||||
this.onFolderExpanding();
|
||||
}
|
||||
|
||||
section.toggle(section).then((f: any) => {
|
||||
if (this.editable && f.expanded) {
|
||||
if (f.items) {
|
||||
_.each(f.items, i => {
|
||||
i.checked = f.checked;
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
section.toggle(section).then((f: any) => {
|
||||
if (this.editable && f.expanded) {
|
||||
if (f.items) {
|
||||
_.each(f.items, i => {
|
||||
i.checked = f.checked;
|
||||
});
|
||||
|
||||
if (this.onSelectionChanged) {
|
||||
this.onSelectionChanged();
|
||||
if (this.onSelectionChanged) {
|
||||
this.onSelectionChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import coreModule from '../core_module';
|
||||
import config from 'app/core/config';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { promiseToDigest } from '../utils/promiseToDigest';
|
||||
|
||||
export class InvitedCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: any, $routeParams: any, contextSrv: any, backendSrv: any) {
|
||||
constructor($scope: any, $routeParams: any, contextSrv: any) {
|
||||
contextSrv.sidemenu = false;
|
||||
$scope.formModel = {};
|
||||
|
||||
@ -17,15 +19,19 @@ export class InvitedCtrl {
|
||||
};
|
||||
|
||||
$scope.init = () => {
|
||||
backendSrv.get('/api/user/invite/' + $routeParams.code).then((invite: any) => {
|
||||
$scope.formModel.name = invite.name;
|
||||
$scope.formModel.email = invite.email;
|
||||
$scope.formModel.username = invite.email;
|
||||
$scope.formModel.inviteCode = $routeParams.code;
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/user/invite/' + $routeParams.code)
|
||||
.then((invite: any) => {
|
||||
$scope.formModel.name = invite.name;
|
||||
$scope.formModel.email = invite.email;
|
||||
$scope.formModel.username = invite.email;
|
||||
$scope.formModel.inviteCode = $routeParams.code;
|
||||
|
||||
$scope.greeting = invite.name || invite.email || invite.username;
|
||||
$scope.invitedBy = invite.invitedBy;
|
||||
});
|
||||
$scope.greeting = invite.name || invite.email || invite.username;
|
||||
$scope.invitedBy = invite.invitedBy;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.submit = () => {
|
||||
@ -33,9 +39,11 @@ export class InvitedCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
backendSrv.post('/api/user/invite/complete', $scope.formModel).then(() => {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
});
|
||||
getBackendSrv()
|
||||
.post('/api/user/invite/complete', $scope.formModel)
|
||||
.then(() => {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
});
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
@ -1,11 +1,12 @@
|
||||
import coreModule from '../core_module';
|
||||
import config from 'app/core/config';
|
||||
import { BackendSrv } from '../services/backend_srv';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { promiseToDigest } from '../utils/promiseToDigest';
|
||||
|
||||
export class ResetPasswordCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: any, backendSrv: BackendSrv, $location: any) {
|
||||
constructor($scope: any, $location: any) {
|
||||
$scope.formModel = {};
|
||||
$scope.mode = 'send';
|
||||
$scope.ldapEnabled = config.ldapEnabled;
|
||||
@ -31,9 +32,14 @@ export class ResetPasswordCtrl {
|
||||
if (!$scope.sendResetForm.$valid) {
|
||||
return;
|
||||
}
|
||||
backendSrv.post('/api/user/password/send-reset-email', $scope.formModel).then(() => {
|
||||
$scope.mode = 'email-sent';
|
||||
});
|
||||
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/user/password/send-reset-email', $scope.formModel)
|
||||
.then(() => {
|
||||
$scope.mode = 'email-sent';
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.submitReset = () => {
|
||||
@ -46,9 +52,11 @@ export class ResetPasswordCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
backendSrv.post('/api/user/password/reset', $scope.formModel).then(() => {
|
||||
$location.path('login');
|
||||
});
|
||||
getBackendSrv()
|
||||
.post('/api/user/password/reset', $scope.formModel)
|
||||
.then(() => {
|
||||
$location.path('login');
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import config from 'app/core/config';
|
||||
import coreModule from '../core_module';
|
||||
import { getBackendSrv } from '@grafana/runtime/src/services';
|
||||
import { promiseToDigest } from '../utils/promiseToDigest';
|
||||
|
||||
export class SignUpCtrl {
|
||||
/** @ngInject */
|
||||
constructor(private $scope: any, private backendSrv: any, $location: any, contextSrv: any) {
|
||||
constructor(private $scope: any, $location: any, contextSrv: any) {
|
||||
contextSrv.sidemenu = false;
|
||||
$scope.ctrl = this;
|
||||
|
||||
@ -34,10 +36,14 @@ export class SignUpCtrl {
|
||||
},
|
||||
};
|
||||
|
||||
backendSrv.get('/api/user/signup/options').then((options: any) => {
|
||||
$scope.verifyEmailEnabled = options.verifyEmailEnabled;
|
||||
$scope.autoAssignOrg = options.autoAssignOrg;
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/user/signup/options')
|
||||
.then((options: any) => {
|
||||
$scope.verifyEmailEnabled = options.verifyEmailEnabled;
|
||||
$scope.autoAssignOrg = options.autoAssignOrg;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
submit() {
|
||||
@ -45,13 +51,15 @@ export class SignUpCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
this.backendSrv.post('/api/user/signup/step2', this.$scope.formModel).then((rsp: any) => {
|
||||
if (rsp.code === 'redirect-to-select-org') {
|
||||
window.location.href = config.appSubUrl + '/profile/select-org?signup=1';
|
||||
} else {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
}
|
||||
});
|
||||
getBackendSrv()
|
||||
.post('/api/user/signup/step2', this.$scope.formModel)
|
||||
.then((rsp: any) => {
|
||||
if (rsp.code === 'redirect-to-select-org') {
|
||||
window.location.href = config.appSubUrl + '/profile/select-org?signup=1';
|
||||
} else {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,57 +1,119 @@
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import { from, merge, MonoTypeOperatorFunction, Observable, Subject, throwError } from 'rxjs';
|
||||
import { catchError, filter, map, mergeMap, retryWhen, share, takeUntil, tap } from 'rxjs/operators';
|
||||
import { fromFetch } from 'rxjs/fetch';
|
||||
import { BackendSrv as BackendService, BackendSrvRequest } from '@grafana/runtime';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
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';
|
||||
import { CoreEvents, DashboardDTO, FolderInfo } from 'app/types';
|
||||
import { ContextSrv, contextSrv } from './context_srv';
|
||||
import { coreModule } from 'app/core/core_module';
|
||||
import { Emitter } from '../utils/emitter';
|
||||
|
||||
export interface DatasourceRequestOptions {
|
||||
retry?: number;
|
||||
method?: string;
|
||||
requestId?: string;
|
||||
timeout?: angular.IPromise<any>;
|
||||
timeout?: Promise<any>;
|
||||
url?: string;
|
||||
headers?: { [key: string]: any };
|
||||
headers?: Record<string, any>;
|
||||
silent?: boolean;
|
||||
data?: { [key: string]: any };
|
||||
data?: Record<string, any>;
|
||||
}
|
||||
|
||||
interface FetchResponseProps {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface ErrorResponseProps extends FetchResponseProps {
|
||||
status?: string;
|
||||
error?: string | any;
|
||||
}
|
||||
|
||||
export interface FetchResponse<T extends FetchResponseProps = any> {
|
||||
status: number;
|
||||
statusText: string;
|
||||
ok: boolean;
|
||||
data: T;
|
||||
}
|
||||
|
||||
interface SuccessResponse extends FetchResponseProps, Record<any, any> {}
|
||||
|
||||
interface DataSourceSuccessResponse<T extends {} = any> {
|
||||
data: T;
|
||||
}
|
||||
|
||||
interface ErrorResponse<T extends ErrorResponseProps = any> {
|
||||
status: number;
|
||||
statusText?: string;
|
||||
isHandled?: boolean;
|
||||
data: T | string;
|
||||
cancelled?: boolean;
|
||||
}
|
||||
|
||||
function serializeParams(data: Record<string, any>): string {
|
||||
return Object.keys(data)
|
||||
.map(key => {
|
||||
const value = data[key];
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(arrayValue => `${encodeURIComponent(key)}=${encodeURIComponent(arrayValue)}`).join('&');
|
||||
}
|
||||
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
||||
})
|
||||
.join('&');
|
||||
}
|
||||
|
||||
export interface BackendSrvDependencies {
|
||||
fromFetch: (input: string | Request, init?: RequestInit) => Observable<Response>;
|
||||
appEvents: Emitter;
|
||||
contextSrv: ContextSrv;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export class BackendSrv implements BackendService {
|
||||
private inFlightRequests: { [key: string]: Array<angular.IDeferred<any>> } = {};
|
||||
private inFlightRequests: Subject<string> = new Subject<string>();
|
||||
private HTTP_REQUEST_CANCELED = -1;
|
||||
private noBackendCache: boolean;
|
||||
private dependencies: BackendSrvDependencies = {
|
||||
fromFetch: fromFetch,
|
||||
appEvents: appEvents,
|
||||
contextSrv: contextSrv,
|
||||
logout: () => {
|
||||
window.location.href = config.appSubUrl + '/logout';
|
||||
},
|
||||
};
|
||||
|
||||
/** @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 });
|
||||
constructor(deps?: BackendSrvDependencies) {
|
||||
if (deps) {
|
||||
this.dependencies = {
|
||||
...this.dependencies,
|
||||
...deps,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
delete(url: string) {
|
||||
return this.request({ method: 'DELETE', url });
|
||||
async get(url: string, params?: any) {
|
||||
return await this.request({ method: 'GET', url, params });
|
||||
}
|
||||
|
||||
post(url: string, data?: any) {
|
||||
return this.request({ method: 'POST', url, data });
|
||||
async delete(url: string) {
|
||||
return await this.request({ method: 'DELETE', url });
|
||||
}
|
||||
|
||||
patch(url: string, data: any) {
|
||||
return this.request({ method: 'PATCH', url, data });
|
||||
async post(url: string, data?: any) {
|
||||
return await this.request({ method: 'POST', url, data });
|
||||
}
|
||||
|
||||
put(url: string, data: any) {
|
||||
return this.request({ method: 'PUT', url, data });
|
||||
async patch(url: string, data: any) {
|
||||
return await this.request({ method: 'PATCH', url, data });
|
||||
}
|
||||
|
||||
async put(url: string, data: any) {
|
||||
return await this.request({ method: 'PUT', url, data });
|
||||
}
|
||||
|
||||
withNoBackendCache(callback: any) {
|
||||
@ -61,18 +123,18 @@ export class BackendSrv implements BackendService {
|
||||
});
|
||||
}
|
||||
|
||||
requestErrorHandler(err: any) {
|
||||
requestErrorHandler = (err: ErrorResponse) => {
|
||||
if (err.isHandled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = err.data || { message: 'Unexpected error' };
|
||||
if (_.isString(data)) {
|
||||
let data = err.data ?? { message: 'Unexpected error' };
|
||||
if (typeof data === 'string') {
|
||||
data = { message: data };
|
||||
}
|
||||
|
||||
if (err.status === 422) {
|
||||
appEvents.emit(AppEvents.alertWarning, ['Validation failed', data.message]);
|
||||
this.dependencies.appEvents.emit(AppEvents.alertWarning, ['Validation failed', data.message]);
|
||||
throw data;
|
||||
}
|
||||
|
||||
@ -84,170 +146,133 @@ export class BackendSrv implements BackendService {
|
||||
message = 'Error';
|
||||
}
|
||||
|
||||
appEvents.emit(err.status < 500 ? AppEvents.alertWarning : AppEvents.alertError, [message, description]);
|
||||
this.dependencies.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;
|
||||
async request(options: BackendSrvRequest): Promise<any> {
|
||||
options = this.parseRequestOptions(options, this.dependencies.contextSrv.user?.orgId);
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
const fromFetchStream = this.getFromFetchStream(options);
|
||||
const failureStream = fromFetchStream.pipe(this.toFailureStream(options));
|
||||
const successStream = fromFetchStream.pipe(
|
||||
filter(response => response.ok === true),
|
||||
map(response => {
|
||||
const fetchSuccessResponse: SuccessResponse = response.data;
|
||||
return fetchSuccessResponse;
|
||||
}),
|
||||
tap(response => {
|
||||
if (options.method !== 'GET' && response?.message && options.showSuccessAlert !== false) {
|
||||
this.dependencies.appEvents.emit(AppEvents.alertSuccess, [response.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];
|
||||
}
|
||||
return merge(successStream, failureStream)
|
||||
.pipe(
|
||||
catchError((err: ErrorResponse) => {
|
||||
if (err.status === 401) {
|
||||
this.dependencies.logout();
|
||||
return throwError(err);
|
||||
}
|
||||
|
||||
// this setTimeout hack enables any caller catching this err to set isHandled to true
|
||||
setTimeout(() => this.requestErrorHandler(err), 50);
|
||||
return throwError(err);
|
||||
})
|
||||
)
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
resolveCancelerIfExists(requestId: string) {
|
||||
const cancelers = this.inFlightRequests[requestId];
|
||||
if (!_.isUndefined(cancelers) && cancelers.length) {
|
||||
cancelers[0].resolve();
|
||||
}
|
||||
this.inFlightRequests.next(requestId);
|
||||
}
|
||||
|
||||
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);
|
||||
async datasourceRequest(options: BackendSrvRequest): Promise<any> {
|
||||
// A requestId is provided by the datasource as a unique identifier for a
|
||||
// particular query. Every observable below has a takeUntil that subscribes to this.inFlightRequests and
|
||||
// will cancel/unsubscribe that observable when a new datasourceRequest with the same requestId is made
|
||||
if (options.requestId) {
|
||||
this.inFlightRequests.next(options.requestId);
|
||||
}
|
||||
|
||||
const requestIsLocal = !options.url.match(/^http/);
|
||||
const firstAttempt = options.retry === 0;
|
||||
options = this.parseDataSourceRequestOptions(
|
||||
options,
|
||||
this.dependencies.contextSrv.user?.orgId,
|
||||
this.noBackendCache
|
||||
);
|
||||
|
||||
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) => {
|
||||
const fromFetchStream = this.getFromFetchStream(options);
|
||||
const failureStream = fromFetchStream.pipe(this.toFailureStream(options));
|
||||
const successStream = fromFetchStream.pipe(
|
||||
filter(response => response.ok === true),
|
||||
map(response => {
|
||||
const { data } = response;
|
||||
const fetchSuccessResponse: DataSourceSuccessResponse = { data };
|
||||
return fetchSuccessResponse;
|
||||
}),
|
||||
tap(res => {
|
||||
if (!options.silent) {
|
||||
appEvents.emit(CoreEvents.dsRequestResponse, response);
|
||||
this.dependencies.appEvents.emit(CoreEvents.dsRequestResponse, res);
|
||||
}
|
||||
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;
|
||||
}
|
||||
return merge(successStream, failureStream)
|
||||
.pipe(
|
||||
catchError((err: ErrorResponse) => {
|
||||
if (err.status === this.HTTP_REQUEST_CANCELED) {
|
||||
return throwError({
|
||||
err,
|
||||
cancelled: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// populate error obj on Internal Error
|
||||
if (_.isString(err.data) && err.status === 500) {
|
||||
err.data = {
|
||||
error: err.statusText,
|
||||
response: err.data,
|
||||
};
|
||||
}
|
||||
if (err.status === 401) {
|
||||
this.dependencies.logout();
|
||||
return throwError(err);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
// populate error obj on Internal Error
|
||||
if (typeof err.data === 'string' && err.status === 500) {
|
||||
err.data = {
|
||||
error: err.statusText,
|
||||
response: err.data,
|
||||
};
|
||||
}
|
||||
|
||||
// for Prometheus
|
||||
if (err.data && !err.data.message && typeof err.data.error === 'string') {
|
||||
err.data.message = err.data.error;
|
||||
}
|
||||
|
||||
if (!options.silent) {
|
||||
this.dependencies.appEvents.emit(CoreEvents.dsRequestError, err);
|
||||
}
|
||||
|
||||
return throwError(err);
|
||||
}),
|
||||
takeUntil(
|
||||
this.inFlightRequests.pipe(
|
||||
filter(requestId => {
|
||||
let cancelRequest = false;
|
||||
if (options && options.requestId && options.requestId === requestId) {
|
||||
// when a new requestId is started it will be published to inFlightRequests
|
||||
// if a previous long running request that hasn't finished yet has the same requestId
|
||||
// we need to cancel that request
|
||||
cancelRequest = true;
|
||||
}
|
||||
return cancelRequest;
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
loginPing() {
|
||||
@ -309,7 +334,7 @@ export class BackendSrv implements BackendService {
|
||||
tasks.push(this.createTask(this.deleteDashboard.bind(this), true, dashboardUid, true));
|
||||
}
|
||||
|
||||
return this.executeInOrder(tasks, []);
|
||||
return this.executeInOrder(tasks);
|
||||
}
|
||||
|
||||
moveDashboards(dashboardUids: string[], toFolder: FolderInfo) {
|
||||
@ -319,82 +344,184 @@ export class BackendSrv implements BackendService {
|
||||
tasks.push(this.createTask(this.moveDashboard.bind(this), true, uid, toFolder));
|
||||
}
|
||||
|
||||
return this.executeInOrder(tasks, []).then((result: any) => {
|
||||
return this.executeInOrder(tasks).then((result: any) => {
|
||||
return {
|
||||
totalCount: result.length,
|
||||
successCount: _.filter(result, { succeeded: true }).length,
|
||||
alreadyInFolderCount: _.filter(result, { alreadyInFolder: true }).length,
|
||||
successCount: result.filter((res: any) => res.succeeded).length,
|
||||
alreadyInFolderCount: result.filter((res: any) => res.alreadyInFolder).length,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private moveDashboard(uid: string, toFolder: FolderInfo) {
|
||||
const deferred = this.$q.defer();
|
||||
private async moveDashboard(uid: string, toFolder: FolderInfo) {
|
||||
const fullDash: DashboardDTO = await this.getDashboardByUid(uid);
|
||||
const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
|
||||
|
||||
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) {
|
||||
return { alreadyInFolder: true };
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
try {
|
||||
await this.saveDashboard(clone, options);
|
||||
return { succeeded: true };
|
||||
} catch (err) {
|
||||
if (err.data?.status !== 'plugin-dashboard') {
|
||||
return { succeeded: false };
|
||||
}
|
||||
|
||||
const clone = model.getSaveModelClone();
|
||||
const options = {
|
||||
folderId: toFolder.id,
|
||||
overwrite: false,
|
||||
};
|
||||
err.isHandled = true;
|
||||
options.overwrite = true;
|
||||
|
||||
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;
|
||||
try {
|
||||
await this.saveDashboard(clone, options);
|
||||
return { succeeded: true };
|
||||
} catch (e) {
|
||||
return { succeeded: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
private createTask(fn: (...args: any[]) => Promise<any>, ignoreRejections: boolean, ...args: any[]) {
|
||||
return async (result: any) => {
|
||||
try {
|
||||
const res = await fn(...args);
|
||||
return Array.prototype.concat(result, [res]);
|
||||
} catch (err) {
|
||||
if (ignoreRejections) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private executeInOrder(tasks: any[], initialValue: any[]) {
|
||||
return tasks.reduce(this.$q.when, initialValue);
|
||||
private executeInOrder(tasks: any[]) {
|
||||
return tasks.reduce((acc, task) => {
|
||||
return Promise.resolve(acc).then(task);
|
||||
}, []);
|
||||
}
|
||||
|
||||
private parseRequestOptions = (options: BackendSrvRequest, orgId?: number): BackendSrvRequest => {
|
||||
options.retry = options.retry ?? 0;
|
||||
const requestIsLocal = !options.url.match(/^http/);
|
||||
|
||||
if (requestIsLocal) {
|
||||
if (orgId) {
|
||||
options.headers = options.headers ?? {};
|
||||
options.headers['X-Grafana-Org-Id'] = orgId;
|
||||
}
|
||||
|
||||
if (options.url.startsWith('/')) {
|
||||
options.url = options.url.substring(1);
|
||||
}
|
||||
|
||||
if (options.url.endsWith('/')) {
|
||||
options.url = options.url.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
private parseDataSourceRequestOptions = (
|
||||
options: BackendSrvRequest,
|
||||
orgId?: number,
|
||||
noBackendCache?: boolean
|
||||
): BackendSrvRequest => {
|
||||
options.retry = options.retry ?? 0;
|
||||
const requestIsLocal = !options.url.match(/^http/);
|
||||
|
||||
if (requestIsLocal) {
|
||||
if (orgId) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers['X-Grafana-Org-Id'] = orgId;
|
||||
}
|
||||
|
||||
if (options.url.startsWith('/')) {
|
||||
options.url = options.url.substring(1);
|
||||
}
|
||||
|
||||
if (options.headers?.Authorization) {
|
||||
options.headers['X-DS-Authorization'] = options.headers.Authorization;
|
||||
delete options.headers.Authorization;
|
||||
}
|
||||
|
||||
if (noBackendCache) {
|
||||
options.headers['X-Grafana-NoCache'] = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
private parseUrlFromOptions = (options: BackendSrvRequest): string => {
|
||||
const cleanParams = omitBy(options.params, v => v === undefined || (v && v.length === 0));
|
||||
const serializedParams = serializeParams(cleanParams);
|
||||
return options.params && serializedParams.length ? `${options.url}?${serializedParams}` : options.url;
|
||||
};
|
||||
|
||||
private parseInitFromOptions = (options: BackendSrvRequest): RequestInit => ({
|
||||
method: options.method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
...options.headers,
|
||||
},
|
||||
body: JSON.stringify(options.data),
|
||||
});
|
||||
|
||||
private getFromFetchStream = (options: BackendSrvRequest) => {
|
||||
const url = this.parseUrlFromOptions(options);
|
||||
const init = this.parseInitFromOptions(options);
|
||||
return this.dependencies.fromFetch(url, init).pipe(
|
||||
mergeMap(async response => {
|
||||
const { status, statusText, ok } = response;
|
||||
const textData = await response.text(); // this could be just a string, prometheus requests for instance
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(textData); // majority of the requests this will be something that can be parsed
|
||||
} catch {
|
||||
data = textData;
|
||||
}
|
||||
const fetchResponse: FetchResponse = { status, statusText, ok, data };
|
||||
return fetchResponse;
|
||||
}),
|
||||
share() // sharing this so we can split into success and failure and then merge back
|
||||
);
|
||||
};
|
||||
|
||||
private toFailureStream = (options: BackendSrvRequest): MonoTypeOperatorFunction<FetchResponse> => inputStream =>
|
||||
inputStream.pipe(
|
||||
filter(response => response.ok === false),
|
||||
mergeMap(response => {
|
||||
const { status, statusText, data } = response;
|
||||
const fetchErrorResponse: ErrorResponse = { status, statusText, data };
|
||||
return throwError(fetchErrorResponse);
|
||||
}),
|
||||
retryWhen((attempts: Observable<any>) =>
|
||||
attempts.pipe(
|
||||
mergeMap((error, i) => {
|
||||
const firstAttempt = i === 0 && options.retry === 0;
|
||||
|
||||
if (error.status === 401 && this.dependencies.contextSrv.user.isSignedIn && firstAttempt) {
|
||||
return from(this.loginPing());
|
||||
}
|
||||
|
||||
return throwError(error);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
coreModule.service('backendSrv', BackendSrv);
|
||||
|
||||
coreModule.factory('backendSrv', () => backendSrv);
|
||||
// Used for testing and things that really need BackendSrv
|
||||
export function getBackendSrv(): BackendSrv {
|
||||
return getBackendService() as BackendSrv;
|
||||
}
|
||||
export const backendSrv = new BackendSrv();
|
||||
export const getBackendSrv = (): BackendSrv => backendSrv;
|
||||
|
@ -43,7 +43,7 @@ export class PerformanceBackend implements EchoBackend<PerformanceEvent, Perform
|
||||
|
||||
// TODO: Enable backend request when we have metrics API
|
||||
// if (this.options.url) {
|
||||
// getBackendSrv().post(this.options.url, result);
|
||||
// backendSrv.post(this.options.url, result);
|
||||
// }
|
||||
};
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import coreModule from 'app/core/core_module';
|
||||
import impressionSrv from 'app/core/services/impression_srv';
|
||||
import store from 'app/core/store';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { BackendSrv } from './backend_srv';
|
||||
import { backendSrv } from './backend_srv';
|
||||
import { Section } from '../components/manage_dashboards/manage_dashboards';
|
||||
import { DashboardSearchHit } from 'app/types/search';
|
||||
|
||||
@ -16,8 +16,7 @@ export class SearchSrv {
|
||||
recentIsOpen: boolean;
|
||||
starredIsOpen: boolean;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv) {
|
||||
constructor() {
|
||||
this.recentIsOpen = store.getBool('search.sections.recent', true);
|
||||
this.starredIsOpen = store.getBool('search.sections.starred', true);
|
||||
}
|
||||
@ -44,7 +43,7 @@ export class SearchSrv {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
return this.backendSrv.search({ dashboardIds: dashIds }).then(result => {
|
||||
return backendSrv.search({ dashboardIds: dashIds }).then(result => {
|
||||
return dashIds
|
||||
.map(orderId => {
|
||||
return _.find(result, { id: orderId });
|
||||
@ -78,7 +77,7 @@ export class SearchSrv {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.backendSrv.search({ starred: true, limit: 30 }).then(result => {
|
||||
return backendSrv.search({ starred: true, limit: 30 }).then(result => {
|
||||
if (result.length > 0) {
|
||||
sections['starred'] = {
|
||||
title: 'Starred',
|
||||
@ -116,7 +115,7 @@ export class SearchSrv {
|
||||
}
|
||||
|
||||
promises.push(
|
||||
this.backendSrv.search(query).then(results => {
|
||||
backendSrv.search(query).then(results => {
|
||||
return this.handleSearchResult(sections, results);
|
||||
})
|
||||
);
|
||||
@ -197,14 +196,14 @@ export class SearchSrv {
|
||||
folderIds: [section.id],
|
||||
};
|
||||
|
||||
return this.backendSrv.search(query).then(results => {
|
||||
return backendSrv.search(query).then(results => {
|
||||
section.items = results;
|
||||
return Promise.resolve(section);
|
||||
});
|
||||
}
|
||||
|
||||
getDashboardTags() {
|
||||
return this.backendSrv.get('/api/dashboards/tags');
|
||||
return backendSrv.get('/api/dashboards/tags');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,13 @@
|
||||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import { getBackendSrv } from '@grafana/runtime/src/services/backendSrv';
|
||||
import { OrgSwitcher } from '../components/OrgSwitcher';
|
||||
import { shallow } from 'enzyme';
|
||||
import { OrgRole } from '@grafana/data';
|
||||
|
||||
const getMock = jest.fn(() => Promise.resolve([]));
|
||||
const postMock = jest.fn();
|
||||
const postMock = jest.fn().mockImplementation(jest.fn());
|
||||
|
||||
jest.mock('@grafana/runtime/src/services/backendSrv', () => ({
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getBackendSrv: () => ({
|
||||
get: getMock,
|
||||
get: jest.fn().mockResolvedValue([]),
|
||||
post: postMock,
|
||||
}),
|
||||
}));
|
||||
|
@ -1,32 +1,532 @@
|
||||
import angular from 'angular';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { ContextSrv } from '../services/context_srv';
|
||||
jest.mock('app/core/store');
|
||||
import { BackendSrv, getBackendSrv } from '../services/backend_srv';
|
||||
import { Emitter } from '../utils/emitter';
|
||||
import { ContextSrv, User } from '../services/context_srv';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { CoreEvents } from '../../types';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
describe('backend_srv', () => {
|
||||
const _httpBackend = (options: any) => {
|
||||
if (options.url === 'gateway-error') {
|
||||
return Promise.reject({ status: 502 });
|
||||
}
|
||||
return Promise.resolve({});
|
||||
const getTestContext = (overides?: object) => {
|
||||
const defaults = {
|
||||
data: { test: 'hello world' },
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'Ok',
|
||||
isSignedIn: true,
|
||||
orgId: 1337,
|
||||
};
|
||||
const props = { ...defaults, ...overides };
|
||||
const textMock = jest.fn().mockResolvedValue(JSON.stringify(props.data));
|
||||
const fromFetchMock = jest.fn().mockImplementation(() => {
|
||||
return of({
|
||||
ok: props.ok,
|
||||
status: props.status,
|
||||
statusText: props.statusText,
|
||||
text: textMock,
|
||||
});
|
||||
});
|
||||
const appEventsMock: Emitter = ({
|
||||
emit: jest.fn(),
|
||||
} as any) as Emitter;
|
||||
const user: User = ({
|
||||
isSignedIn: props.isSignedIn,
|
||||
orgId: props.orgId,
|
||||
} as any) as User;
|
||||
const contextSrvMock: ContextSrv = ({
|
||||
user,
|
||||
} as any) as ContextSrv;
|
||||
const logoutMock = jest.fn();
|
||||
const parseRequestOptionsMock = jest.fn().mockImplementation(options => options);
|
||||
const parseDataSourceRequestOptionsMock = jest.fn().mockImplementation(options => options);
|
||||
const parseUrlFromOptionsMock = jest.fn().mockImplementation(() => 'parseUrlFromOptionsMock');
|
||||
const parseInitFromOptionsMock = jest.fn().mockImplementation(() => 'parseInitFromOptionsMock');
|
||||
|
||||
const backendSrv = new BackendSrv({
|
||||
fromFetch: fromFetchMock,
|
||||
appEvents: appEventsMock,
|
||||
contextSrv: contextSrvMock,
|
||||
logout: logoutMock,
|
||||
});
|
||||
|
||||
backendSrv['parseRequestOptions'] = parseRequestOptionsMock;
|
||||
backendSrv['parseDataSourceRequestOptions'] = parseDataSourceRequestOptionsMock;
|
||||
backendSrv['parseUrlFromOptions'] = parseUrlFromOptionsMock;
|
||||
backendSrv['parseInitFromOptions'] = parseInitFromOptionsMock;
|
||||
|
||||
const expectCallChain = (options: any) => {
|
||||
expect(parseUrlFromOptionsMock).toHaveBeenCalledTimes(1);
|
||||
expect(parseUrlFromOptionsMock).toHaveBeenCalledWith(options);
|
||||
expect(parseInitFromOptionsMock).toHaveBeenCalledTimes(1);
|
||||
expect(parseInitFromOptionsMock).toHaveBeenCalledWith(options);
|
||||
expect(fromFetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(fromFetchMock).toHaveBeenCalledWith('parseUrlFromOptionsMock', 'parseInitFromOptionsMock');
|
||||
};
|
||||
|
||||
const _backendSrv = new BackendSrv(
|
||||
_httpBackend,
|
||||
{} as angular.IQService,
|
||||
{} as angular.ITimeoutService,
|
||||
{} as ContextSrv
|
||||
);
|
||||
const expectRequestCallChain = (options: any) => {
|
||||
expect(parseRequestOptionsMock).toHaveBeenCalledTimes(1);
|
||||
expect(parseRequestOptionsMock).toHaveBeenCalledWith(options, 1337);
|
||||
expectCallChain(options);
|
||||
};
|
||||
|
||||
describe('when handling errors', () => {
|
||||
it('should return the http status code', async () => {
|
||||
try {
|
||||
await _backendSrv.datasourceRequest({
|
||||
url: 'gateway-error',
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.status).toBe(502);
|
||||
const expectDataSourceRequestCallChain = (options: any) => {
|
||||
expect(parseDataSourceRequestOptionsMock).toHaveBeenCalledTimes(1);
|
||||
expect(parseDataSourceRequestOptionsMock).toHaveBeenCalledWith(options, 1337, undefined);
|
||||
expectCallChain(options);
|
||||
};
|
||||
|
||||
return {
|
||||
backendSrv,
|
||||
fromFetchMock,
|
||||
appEventsMock,
|
||||
contextSrvMock,
|
||||
textMock,
|
||||
logoutMock,
|
||||
parseRequestOptionsMock,
|
||||
parseDataSourceRequestOptionsMock,
|
||||
parseUrlFromOptionsMock,
|
||||
parseInitFromOptionsMock,
|
||||
expectRequestCallChain,
|
||||
expectDataSourceRequestCallChain,
|
||||
};
|
||||
};
|
||||
|
||||
describe('backendSrv', () => {
|
||||
describe('parseRequestOptions', () => {
|
||||
it.each`
|
||||
retry | url | orgId | expected
|
||||
${undefined} | ${'http://localhost:3000/api/dashboard'} | ${undefined} | ${{ retry: 0, url: 'http://localhost:3000/api/dashboard' }}
|
||||
${1} | ${'http://localhost:3000/api/dashboard'} | ${1} | ${{ retry: 1, url: 'http://localhost:3000/api/dashboard' }}
|
||||
${undefined} | ${'api/dashboard'} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
||||
${undefined} | ${'/api/dashboard'} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
||||
${undefined} | ${'/api/dashboard/'} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
||||
${1} | ${'/api/dashboard/'} | ${undefined} | ${{ retry: 1, url: 'api/dashboard' }}
|
||||
${undefined} | ${'/api/dashboard/'} | ${1} | ${{ retry: 0, url: 'api/dashboard', headers: { 'X-Grafana-Org-Id': 1 } }}
|
||||
${1} | ${'/api/dashboard/'} | ${1} | ${{ retry: 1, url: 'api/dashboard', headers: { 'X-Grafana-Org-Id': 1 } }}
|
||||
`(
|
||||
"when called with retry: '$retry', url: '$url' and orgId: '$orgId' then result should be '$expected'",
|
||||
({ retry, url, orgId, expected }) => {
|
||||
expect(getBackendSrv()['parseRequestOptions']({ retry, url }, orgId)).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('parseDataSourceRequestOptions', () => {
|
||||
it.each`
|
||||
retry | url | headers | orgId | noBackendCache | expected
|
||||
${undefined} | ${'http://localhost:3000/api/dashboard'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'http://localhost:3000/api/dashboard' }}
|
||||
${1} | ${'http://localhost:3000/api/dashboard'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${true} | ${{ retry: 1, url: 'http://localhost:3000/api/dashboard', headers: { Authorization: 'Some Auth' } }}
|
||||
${undefined} | ${'api/dashboard'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
||||
${undefined} | ${'/api/dashboard'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
||||
${undefined} | ${'/api/dashboard/'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'api/dashboard/' }}
|
||||
${undefined} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth' } }}
|
||||
${undefined} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${undefined} | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1 } }}
|
||||
${undefined} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${true} | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1, 'X-Grafana-NoCache': 'true' } }}
|
||||
${1} | ${'/api/dashboard/'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 1, url: 'api/dashboard/' }}
|
||||
${1} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${undefined} | ${undefined} | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth' } }}
|
||||
${1} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${undefined} | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1 } }}
|
||||
${1} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${true} | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1, 'X-Grafana-NoCache': 'true' } }}
|
||||
`(
|
||||
"when called with retry: '$retry', url: '$url', headers: '$headers', orgId: '$orgId' and noBackendCache: '$noBackendCache' then result should be '$expected'",
|
||||
({ retry, url, headers, orgId, noBackendCache, expected }) => {
|
||||
expect(
|
||||
getBackendSrv()['parseDataSourceRequestOptions']({ retry, url, headers }, orgId, noBackendCache)
|
||||
).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('parseUrlFromOptions', () => {
|
||||
it.each`
|
||||
params | url | expected
|
||||
${undefined} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
${{ key: 'value' }} | ${'api/dashboard'} | ${'api/dashboard?key=value'}
|
||||
${{ key: undefined }} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
${{ firstKey: 'first value', secondValue: 'second value' }} | ${'api/dashboard'} | ${'api/dashboard?firstKey=first%20value&secondValue=second%20value'}
|
||||
${{ firstKey: 'first value', secondValue: undefined }} | ${'api/dashboard'} | ${'api/dashboard?firstKey=first%20value'}
|
||||
${{ id: [1, 2, 3] }} | ${'api/dashboard'} | ${'api/dashboard?id=1&id=2&id=3'}
|
||||
${{ id: [] }} | ${'api/dashboard'} | ${'api/dashboard'}
|
||||
`(
|
||||
"when called with params: '$params' and url: '$url' then result should be '$expected'",
|
||||
({ params, url, expected }) => {
|
||||
expect(getBackendSrv()['parseUrlFromOptions']({ params, url })).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('parseInitFromOptions', () => {
|
||||
it.each`
|
||||
method | headers | data | expected
|
||||
${undefined} | ${undefined} | ${undefined} | ${{ method: undefined, headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*' }, body: undefined }}
|
||||
${'GET'} | ${undefined} | ${undefined} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*' }, body: undefined }}
|
||||
${'GET'} | ${{ Auth: 'Some Auth' }} | ${undefined} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: undefined }}
|
||||
${'GET'} | ${{ Auth: 'Some Auth' }} | ${{ data: { test: 'Some data' } }} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: '{"data":{"test":"Some data"}}' }}
|
||||
${'GET'} | ${{ Auth: 'Some Auth' }} | ${'some data'} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: '"some data"' }}
|
||||
`(
|
||||
"when called with method: '$method', headers: '$headers' and data: '$data' then result should be '$expected'",
|
||||
({ method, headers, data, expected }) => {
|
||||
expect(getBackendSrv()['parseInitFromOptions']({ method, headers, data, url: '' })).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('request', () => {
|
||||
describe('when making a successful call and conditions for showSuccessAlert are not favorable', () => {
|
||||
it('then it should return correct result and not emit anything', async () => {
|
||||
const { backendSrv, appEventsMock, expectRequestCallChain } = getTestContext({
|
||||
data: { message: 'A message' },
|
||||
});
|
||||
const url = '/api/dashboard/';
|
||||
const result = await backendSrv.request({ url, method: 'DELETE', showSuccessAlert: false });
|
||||
expect(result).toEqual({ message: 'A message' });
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
expectRequestCallChain({ url, method: 'DELETE', showSuccessAlert: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making a successful call and conditions for showSuccessAlert are favorable', () => {
|
||||
it('then it should emit correct message', async () => {
|
||||
const { backendSrv, appEventsMock, expectRequestCallChain } = getTestContext({
|
||||
data: { message: 'A message' },
|
||||
});
|
||||
const url = '/api/dashboard/';
|
||||
const result = await backendSrv.request({ url, method: 'DELETE', showSuccessAlert: true });
|
||||
expect(result).toEqual({ message: 'A message' });
|
||||
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
||||
expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertSuccess, ['A message']);
|
||||
expectRequestCallChain({ url, method: 'DELETE', showSuccessAlert: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making an unsuccessful call and conditions for retry are favorable and loginPing does not throw', () => {
|
||||
it('then it should retry', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: 401,
|
||||
statusText: 'UnAuthorized',
|
||||
data: { message: 'UnAuthorized' },
|
||||
});
|
||||
backendSrv.loginPing = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', data: { message: 'Ok' } });
|
||||
const url = '/api/dashboard/';
|
||||
// it would be better if we could simulate that after the call to loginPing everything is successful but as
|
||||
// our fromFetchMock returns ok:false the second time this retries it will still be ok:false going into the
|
||||
// mergeMap in toFailureStream
|
||||
await backendSrv.request({ url, method: 'GET', retry: 0 }).catch(error => {
|
||||
expect(error.status).toBe(401);
|
||||
expect(error.statusText).toBe('UnAuthorized');
|
||||
expect(error.data).toEqual({ message: 'UnAuthorized' });
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
|
||||
expect(logoutMock).toHaveBeenCalledTimes(1);
|
||||
expectRequestCallChain({ url, method: 'GET', retry: 0 });
|
||||
jest.advanceTimersByTime(50);
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making an unsuccessful call and conditions for retry are favorable and retry throws', () => {
|
||||
it('then it throw error', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: 401,
|
||||
statusText: 'UnAuthorized',
|
||||
data: { message: 'UnAuthorized' },
|
||||
});
|
||||
backendSrv.loginPing = jest
|
||||
.fn()
|
||||
.mockRejectedValue({ status: 403, statusText: 'Forbidden', data: { message: 'Forbidden' } });
|
||||
const url = '/api/dashboard/';
|
||||
await backendSrv
|
||||
.request({ url, method: 'GET', retry: 0 })
|
||||
.catch(error => {
|
||||
expect(error.status).toBe(403);
|
||||
expect(error.statusText).toBe('Forbidden');
|
||||
expect(error.data).toEqual({ message: 'Forbidden' });
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
expectRequestCallChain({ url, method: 'GET', retry: 0 });
|
||||
jest.advanceTimersByTime(50);
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error).toEqual({ message: 'Forbidden' });
|
||||
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
||||
expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertWarning, ['Forbidden', '']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making an unsuccessful 422 call', () => {
|
||||
it('then it should emit Validation failed message', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: 422,
|
||||
statusText: 'Unprocessable Entity',
|
||||
data: { message: 'Unprocessable Entity' },
|
||||
});
|
||||
const url = '/api/dashboard/';
|
||||
await backendSrv
|
||||
.request({ url, method: 'GET' })
|
||||
.catch(error => {
|
||||
expect(error.status).toBe(422);
|
||||
expect(error.statusText).toBe('Unprocessable Entity');
|
||||
expect(error.data).toEqual({ message: 'Unprocessable Entity' });
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
expectRequestCallChain({ url, method: 'GET' });
|
||||
jest.advanceTimersByTime(50);
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error).toEqual({ message: 'Unprocessable Entity' });
|
||||
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
||||
expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertWarning, [
|
||||
'Validation failed',
|
||||
'Unprocessable Entity',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making an unsuccessful call and we handle the error', () => {
|
||||
it('then it should not emit message', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: 404,
|
||||
statusText: 'Not found',
|
||||
data: { message: 'Not found' },
|
||||
});
|
||||
const url = '/api/dashboard/';
|
||||
await backendSrv.request({ url, method: 'GET' }).catch(error => {
|
||||
expect(error.status).toBe(404);
|
||||
expect(error.statusText).toBe('Not found');
|
||||
expect(error.data).toEqual({ message: 'Not found' });
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
expectRequestCallChain({ url, method: 'GET' });
|
||||
error.isHandled = true;
|
||||
jest.advanceTimersByTime(50);
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('datasourceRequest', () => {
|
||||
describe('when making a successful call and silent is true', () => {
|
||||
it('then it should not emit message', async () => {
|
||||
const { backendSrv, appEventsMock, expectDataSourceRequestCallChain } = getTestContext();
|
||||
const url = 'http://www.some.url.com/';
|
||||
const result = await backendSrv.datasourceRequest({ url, silent: true });
|
||||
expect(result).toEqual({ data: { test: 'hello world' } });
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
expectDataSourceRequestCallChain({ url, silent: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making a successful call and silent is not defined', () => {
|
||||
it('then it should not emit message', async () => {
|
||||
const { backendSrv, appEventsMock, expectDataSourceRequestCallChain } = getTestContext();
|
||||
const url = 'http://www.some.url.com/';
|
||||
const result = await backendSrv.datasourceRequest({ url });
|
||||
expect(result).toEqual({ data: { test: 'hello world' } });
|
||||
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
||||
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestResponse, {
|
||||
data: { test: 'hello world' },
|
||||
});
|
||||
expectDataSourceRequestCallChain({ url });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when called with the same requestId twice', () => {
|
||||
it('then it should cancel the first call and the first call should be unsubscribed', async () => {
|
||||
const { backendSrv, fromFetchMock } = getTestContext();
|
||||
const unsubscribe = jest.fn();
|
||||
const slowData = { message: 'Slow Request' };
|
||||
const slowFetch = new Observable(subscriber => {
|
||||
subscriber.next({
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'Ok',
|
||||
text: () => Promise.resolve(JSON.stringify(slowData)),
|
||||
});
|
||||
return unsubscribe;
|
||||
}).pipe(delay(10000));
|
||||
const fastData = { message: 'Fast Request' };
|
||||
const fastFetch = of({
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'Ok',
|
||||
text: () => Promise.resolve(JSON.stringify(fastData)),
|
||||
});
|
||||
fromFetchMock.mockImplementationOnce(() => slowFetch);
|
||||
fromFetchMock.mockImplementation(() => fastFetch);
|
||||
const options = {
|
||||
url: '/api/dashboard/',
|
||||
requestId: 'A',
|
||||
};
|
||||
const slowRequest = backendSrv.datasourceRequest(options);
|
||||
const fastResponse = await backendSrv.datasourceRequest(options);
|
||||
expect(fastResponse).toEqual({ data: { message: 'Fast Request' } });
|
||||
|
||||
const slowResponse = await slowRequest;
|
||||
expect(slowResponse).toEqual(undefined);
|
||||
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making an unsuccessful call and conditions for retry are favorable and loginPing does not throw', () => {
|
||||
it('then it should retry', async () => {
|
||||
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: 401,
|
||||
statusText: 'UnAuthorized',
|
||||
data: { message: 'UnAuthorized' },
|
||||
});
|
||||
backendSrv.loginPing = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', data: { message: 'Ok' } });
|
||||
const url = '/api/dashboard/';
|
||||
// it would be better if we could simulate that after the call to loginPing everything is successful but as
|
||||
// our fromFetchMock returns ok:false the second time this retries it will still be ok:false going into the
|
||||
// mergeMap in toFailureStream
|
||||
await backendSrv.datasourceRequest({ url, method: 'GET', retry: 0 }).catch(error => {
|
||||
expect(error.status).toBe(401);
|
||||
expect(error.statusText).toBe('UnAuthorized');
|
||||
expect(error.data).toEqual({ message: 'UnAuthorized' });
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
|
||||
expect(logoutMock).toHaveBeenCalledTimes(1);
|
||||
expectDataSourceRequestCallChain({ url, method: 'GET', retry: 0 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making an unsuccessful call and conditions for retry are favorable and retry throws', () => {
|
||||
it('then it throw error', async () => {
|
||||
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: 401,
|
||||
statusText: 'UnAuthorized',
|
||||
data: { message: 'UnAuthorized' },
|
||||
});
|
||||
backendSrv.loginPing = jest
|
||||
.fn()
|
||||
.mockRejectedValue({ status: 403, statusText: 'Forbidden', data: { message: 'Forbidden' } });
|
||||
const url = '/api/dashboard/';
|
||||
await backendSrv.datasourceRequest({ url, method: 'GET', retry: 0 }).catch(error => {
|
||||
expect(error.status).toBe(403);
|
||||
expect(error.statusText).toBe('Forbidden');
|
||||
expect(error.data).toEqual({ message: 'Forbidden' });
|
||||
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
||||
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
|
||||
data: { message: 'Forbidden' },
|
||||
status: 403,
|
||||
statusText: 'Forbidden',
|
||||
});
|
||||
expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
expectDataSourceRequestCallChain({ url, method: 'GET', retry: 0 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making a HTTP_REQUEST_CANCELED call', () => {
|
||||
it('then it should throw cancelled error', async () => {
|
||||
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: -1,
|
||||
statusText: 'HTTP_REQUEST_CANCELED',
|
||||
data: { message: 'HTTP_REQUEST_CANCELED' },
|
||||
});
|
||||
const url = '/api/dashboard/';
|
||||
await backendSrv.datasourceRequest({ url, method: 'GET' }).catch(error => {
|
||||
expect(error).toEqual({
|
||||
err: {
|
||||
status: -1,
|
||||
statusText: 'HTTP_REQUEST_CANCELED',
|
||||
data: { message: 'HTTP_REQUEST_CANCELED' },
|
||||
},
|
||||
cancelled: true,
|
||||
});
|
||||
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
expectDataSourceRequestCallChain({ url, method: 'GET' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making an Internal Error call', () => {
|
||||
it('then it should throw cancelled error', async () => {
|
||||
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
data: 'Internal Server Error',
|
||||
});
|
||||
const url = '/api/dashboard/';
|
||||
await backendSrv.datasourceRequest({ url, method: 'GET' }).catch(error => {
|
||||
expect(error).toEqual({
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
data: {
|
||||
error: 'Internal Server Error',
|
||||
response: 'Internal Server Error',
|
||||
message: 'Internal Server Error',
|
||||
},
|
||||
});
|
||||
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
||||
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
data: {
|
||||
error: 'Internal Server Error',
|
||||
response: 'Internal Server Error',
|
||||
message: 'Internal Server Error',
|
||||
},
|
||||
});
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
expectDataSourceRequestCallChain({ url, method: 'GET' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when formatting prometheus error', () => {
|
||||
it('then it should throw cancelled error', async () => {
|
||||
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
||||
ok: false,
|
||||
status: 403,
|
||||
statusText: 'Forbidden',
|
||||
data: { error: 'Forbidden' },
|
||||
});
|
||||
const url = '/api/dashboard/';
|
||||
await backendSrv.datasourceRequest({ url, method: 'GET' }).catch(error => {
|
||||
expect(error).toEqual({
|
||||
status: 403,
|
||||
statusText: 'Forbidden',
|
||||
data: {
|
||||
error: 'Forbidden',
|
||||
message: 'Forbidden',
|
||||
},
|
||||
});
|
||||
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
||||
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
|
||||
status: 403,
|
||||
statusText: 'Forbidden',
|
||||
data: {
|
||||
error: 'Forbidden',
|
||||
message: 'Forbidden',
|
||||
},
|
||||
});
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
expectDataSourceRequestCallChain({ url, method: 'GET' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,6 @@ import {
|
||||
FoldersAndDashboardUids,
|
||||
} from 'app/core/components/manage_dashboards/manage_dashboards';
|
||||
import { SearchSrv } from 'app/core/services/search_srv';
|
||||
import { BackendSrv } from '../services/backend_srv';
|
||||
import { ContextSrv } from '../services/context_srv';
|
||||
|
||||
const mockSection = (overides?: object): Section => {
|
||||
@ -593,7 +592,6 @@ function createCtrlWithStubs(searchResponse: any, tags?: any) {
|
||||
|
||||
return new ManageDashboardsCtrl(
|
||||
{ $digest: jest.fn() } as any,
|
||||
{} as BackendSrv,
|
||||
searchSrvStub as SearchSrv,
|
||||
{ isEditor: true } as ContextSrv
|
||||
);
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
|
||||
import { SearchResultsCtrl } from '../components/search/search_results';
|
||||
import { beforeEach, afterEach } from 'test/lib/common';
|
||||
import { afterEach, beforeEach } from 'test/lib/common';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { CoreEvents } from 'app/types';
|
||||
|
||||
@ -11,13 +13,15 @@ jest.mock('app/core/app_events', () => {
|
||||
|
||||
describe('SearchResultsCtrl', () => {
|
||||
let ctrl: any;
|
||||
const $location = {} as ILocationService;
|
||||
const $scope = ({ $evalAsync: jest.fn() } as any) as IScope;
|
||||
|
||||
describe('when checking an item that is not checked', () => {
|
||||
const item = { checked: false };
|
||||
let selectionChanged = false;
|
||||
|
||||
beforeEach(() => {
|
||||
ctrl = new SearchResultsCtrl({});
|
||||
ctrl = new SearchResultsCtrl($location, $scope);
|
||||
ctrl.onSelectionChanged = () => (selectionChanged = true);
|
||||
ctrl.toggleSelection(item);
|
||||
});
|
||||
@ -36,7 +40,7 @@ describe('SearchResultsCtrl', () => {
|
||||
let selectionChanged = false;
|
||||
|
||||
beforeEach(() => {
|
||||
ctrl = new SearchResultsCtrl({});
|
||||
ctrl = new SearchResultsCtrl($location, $scope);
|
||||
ctrl.onSelectionChanged = () => (selectionChanged = true);
|
||||
ctrl.toggleSelection(item);
|
||||
});
|
||||
@ -54,7 +58,7 @@ describe('SearchResultsCtrl', () => {
|
||||
let selectedTag: any = null;
|
||||
|
||||
beforeEach(() => {
|
||||
ctrl = new SearchResultsCtrl({});
|
||||
ctrl = new SearchResultsCtrl($location, $scope);
|
||||
ctrl.onTagSelected = (tag: any) => (selectedTag = tag);
|
||||
ctrl.selectTag('tag-test');
|
||||
});
|
||||
@ -68,7 +72,7 @@ describe('SearchResultsCtrl', () => {
|
||||
let folderExpanded = false;
|
||||
|
||||
beforeEach(() => {
|
||||
ctrl = new SearchResultsCtrl({});
|
||||
ctrl = new SearchResultsCtrl($location, $scope);
|
||||
ctrl.onFolderExpanding = () => {
|
||||
folderExpanded = true;
|
||||
};
|
||||
@ -90,7 +94,7 @@ describe('SearchResultsCtrl', () => {
|
||||
let folderExpanded = false;
|
||||
|
||||
beforeEach(() => {
|
||||
ctrl = new SearchResultsCtrl({});
|
||||
ctrl = new SearchResultsCtrl($location, $scope);
|
||||
ctrl.onFolderExpanding = () => {
|
||||
folderExpanded = true;
|
||||
};
|
||||
@ -110,12 +114,12 @@ describe('SearchResultsCtrl', () => {
|
||||
|
||||
describe('when clicking on a link in search result', () => {
|
||||
const dashPath = 'dashboard/path';
|
||||
const $location = { path: () => dashPath };
|
||||
const $location = ({ path: () => dashPath } as any) as ILocationService;
|
||||
const appEventsMock = appEvents as any;
|
||||
|
||||
describe('with the same url as current path', () => {
|
||||
beforeEach(() => {
|
||||
ctrl = new SearchResultsCtrl($location);
|
||||
ctrl = new SearchResultsCtrl($location, $scope);
|
||||
const item = { url: dashPath };
|
||||
ctrl.onItemClick(item);
|
||||
});
|
||||
@ -128,7 +132,7 @@ describe('SearchResultsCtrl', () => {
|
||||
|
||||
describe('with a different url than current path', () => {
|
||||
beforeEach(() => {
|
||||
ctrl = new SearchResultsCtrl($location);
|
||||
ctrl = new SearchResultsCtrl($location, $scope);
|
||||
const item = { url: 'another/path' };
|
||||
ctrl.onItemClick(item);
|
||||
});
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { SearchSrv } from 'app/core/services/search_srv';
|
||||
import { BackendSrvMock } from 'test/mocks/backend_srv';
|
||||
import impressionSrv from 'app/core/services/impression_srv';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { beforeEach } from 'test/lib/common';
|
||||
import { BackendSrv } from '../services/backend_srv';
|
||||
import { backendSrv } from '../services/backend_srv';
|
||||
|
||||
jest.mock('app/core/store', () => {
|
||||
return {
|
||||
@ -19,29 +18,32 @@ jest.mock('app/core/services/impression_srv', () => {
|
||||
});
|
||||
|
||||
describe('SearchSrv', () => {
|
||||
let searchSrv: SearchSrv, backendSrvMock: BackendSrvMock;
|
||||
let searchSrv: SearchSrv;
|
||||
const searchMock = jest.spyOn(backendSrv, 'search'); // will use the mock in __mocks__
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock = new BackendSrvMock();
|
||||
searchSrv = new SearchSrv(backendSrvMock as BackendSrv);
|
||||
searchSrv = new SearchSrv();
|
||||
|
||||
contextSrv.isSignedIn = true;
|
||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('With recent dashboards', () => {
|
||||
let results: any;
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{ id: 2, title: 'second but first' },
|
||||
{ id: 1, title: 'first but second' },
|
||||
])
|
||||
)
|
||||
.mockReturnValue(Promise.resolve([]));
|
||||
searchMock.mockImplementation(
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{ id: 2, title: 'second but first' },
|
||||
{ id: 1, title: 'first but second' },
|
||||
])
|
||||
)
|
||||
.mockReturnValue(Promise.resolve([]))
|
||||
);
|
||||
|
||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
|
||||
|
||||
@ -63,15 +65,17 @@ describe('SearchSrv', () => {
|
||||
let results: any;
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{ id: 2, title: 'two' },
|
||||
{ id: 1, title: 'one' },
|
||||
])
|
||||
)
|
||||
.mockReturnValue(Promise.resolve([]));
|
||||
searchMock.mockImplementation(
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{ id: 2, title: 'two' },
|
||||
{ id: 1, title: 'one' },
|
||||
])
|
||||
)
|
||||
.mockReturnValue(Promise.resolve([]))
|
||||
);
|
||||
|
||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([4, 5, 1, 2, 3]);
|
||||
|
||||
@ -92,7 +96,7 @@ describe('SearchSrv', () => {
|
||||
let results: any;
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest.fn().mockReturnValue(Promise.resolve([{ id: 1, title: 'starred' }]));
|
||||
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([{ id: 1, title: 'starred' }])));
|
||||
|
||||
return searchSrv.search({ query: '' }).then(res => {
|
||||
results = res;
|
||||
@ -109,15 +113,17 @@ describe('SearchSrv', () => {
|
||||
let results: any;
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{ id: 1, title: 'starred and recent', isStarred: true },
|
||||
{ id: 2, title: 'recent' },
|
||||
])
|
||||
)
|
||||
.mockReturnValue(Promise.resolve([{ id: 1, title: 'starred and recent' }]));
|
||||
searchMock.mockImplementation(
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{ id: 1, title: 'starred and recent', isStarred: true },
|
||||
{ id: 2, title: 'recent' },
|
||||
])
|
||||
)
|
||||
.mockReturnValue(Promise.resolve([{ id: 1, title: 'starred and recent' }]))
|
||||
);
|
||||
|
||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
|
||||
return searchSrv.search({ query: '' }).then(res => {
|
||||
@ -140,35 +146,37 @@ describe('SearchSrv', () => {
|
||||
let results: any;
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(Promise.resolve([]))
|
||||
.mockReturnValue(
|
||||
Promise.resolve([
|
||||
{
|
||||
title: 'folder1',
|
||||
type: 'dash-folder',
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
title: 'dash with no folder',
|
||||
type: 'dash-db',
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
title: 'dash in folder1 1',
|
||||
type: 'dash-db',
|
||||
id: 3,
|
||||
folderId: 1,
|
||||
},
|
||||
{
|
||||
title: 'dash in folder1 2',
|
||||
type: 'dash-db',
|
||||
id: 4,
|
||||
folderId: 1,
|
||||
},
|
||||
])
|
||||
);
|
||||
searchMock.mockImplementation(
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(Promise.resolve([]))
|
||||
.mockReturnValue(
|
||||
Promise.resolve([
|
||||
{
|
||||
title: 'folder1',
|
||||
type: 'dash-folder',
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
title: 'dash with no folder',
|
||||
type: 'dash-db',
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
title: 'dash in folder1 1',
|
||||
type: 'dash-db',
|
||||
id: 3,
|
||||
folderId: 1,
|
||||
},
|
||||
{
|
||||
title: 'dash in folder1 2',
|
||||
type: 'dash-db',
|
||||
id: 4,
|
||||
folderId: 1,
|
||||
},
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
return searchSrv.search({ query: '' }).then(res => {
|
||||
results = res;
|
||||
@ -188,23 +196,25 @@ describe('SearchSrv', () => {
|
||||
let results: any;
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest.fn().mockReturnValue(
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 2,
|
||||
title: 'dash with no folder',
|
||||
type: 'dash-db',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'dash in folder1 1',
|
||||
type: 'dash-db',
|
||||
folderId: 1,
|
||||
folderUid: 'uid',
|
||||
folderTitle: 'folder1',
|
||||
folderUrl: '/dashboards/f/uid/folder1',
|
||||
},
|
||||
])
|
||||
searchMock.mockImplementation(
|
||||
jest.fn().mockReturnValue(
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 2,
|
||||
title: 'dash with no folder',
|
||||
type: 'dash-db',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'dash in folder1 1',
|
||||
type: 'dash-db',
|
||||
folderId: 1,
|
||||
folderUid: 'uid',
|
||||
folderTitle: 'folder1',
|
||||
folderUrl: '/dashboards/f/uid/folder1',
|
||||
},
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
return searchSrv.search({ query: 'search' }).then(res => {
|
||||
@ -213,7 +223,7 @@ describe('SearchSrv', () => {
|
||||
});
|
||||
|
||||
it('should not specify folder ids', () => {
|
||||
expect(backendSrvMock.search.mock.calls[0][0].folderIds).toHaveLength(0);
|
||||
expect(searchMock.mock.calls[0][0].folderIds).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should group results by folder', () => {
|
||||
@ -228,27 +238,25 @@ describe('SearchSrv', () => {
|
||||
|
||||
describe('with tags', () => {
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest.fn();
|
||||
backendSrvMock.search.mockReturnValue(Promise.resolve([]));
|
||||
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([])));
|
||||
|
||||
return searchSrv.search({ tag: ['atag'] }).then(() => {});
|
||||
});
|
||||
|
||||
it('should send tags query to backend search', () => {
|
||||
expect(backendSrvMock.search.mock.calls[0][0].tag).toHaveLength(1);
|
||||
expect(searchMock.mock.calls[0][0].tag).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with starred', () => {
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest.fn();
|
||||
backendSrvMock.search.mockReturnValue(Promise.resolve([]));
|
||||
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([])));
|
||||
|
||||
return searchSrv.search({ starred: true }).then(() => {});
|
||||
});
|
||||
|
||||
it('should send starred query to backend search', () => {
|
||||
expect(backendSrvMock.search.mock.calls[0][0].starred).toEqual(true);
|
||||
expect(searchMock.mock.calls[0][0].starred).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -256,8 +264,7 @@ describe('SearchSrv', () => {
|
||||
let getRecentDashboardsCalled = false;
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest.fn();
|
||||
backendSrvMock.search.mockReturnValue(Promise.resolve([]));
|
||||
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([])));
|
||||
|
||||
searchSrv['getRecentDashboards'] = () => {
|
||||
getRecentDashboardsCalled = true;
|
||||
@ -276,8 +283,7 @@ describe('SearchSrv', () => {
|
||||
let getStarredCalled = false;
|
||||
|
||||
beforeEach(() => {
|
||||
backendSrvMock.search = jest.fn();
|
||||
backendSrvMock.search.mockReturnValue(Promise.resolve([]));
|
||||
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([])));
|
||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]);
|
||||
|
||||
searchSrv['getStarred'] = () => {
|
||||
|
@ -1,28 +1,32 @@
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
|
||||
export default class AdminEditOrgCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: any, $routeParams: any, backendSrv: BackendSrv, $location: any, navModelSrv: NavModelSrv) {
|
||||
constructor($scope: any, $routeParams: any, $location: any, navModelSrv: NavModelSrv) {
|
||||
$scope.init = () => {
|
||||
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
|
||||
|
||||
if ($routeParams.id) {
|
||||
$scope.getOrg($routeParams.id);
|
||||
$scope.getOrgUsers($routeParams.id);
|
||||
promiseToDigest($scope)(Promise.all([$scope.getOrg($routeParams.id), $scope.getOrgUsers($routeParams.id)]));
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getOrg = (id: number) => {
|
||||
backendSrv.get('/api/orgs/' + id).then((org: any) => {
|
||||
$scope.org = org;
|
||||
});
|
||||
return getBackendSrv()
|
||||
.get('/api/orgs/' + id)
|
||||
.then((org: any) => {
|
||||
$scope.org = org;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getOrgUsers = (id: number) => {
|
||||
backendSrv.get('/api/orgs/' + id + '/users').then((orgUsers: any) => {
|
||||
$scope.orgUsers = orgUsers;
|
||||
});
|
||||
return getBackendSrv()
|
||||
.get('/api/orgs/' + id + '/users')
|
||||
.then((orgUsers: any) => {
|
||||
$scope.orgUsers = orgUsers;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.update = () => {
|
||||
@ -30,19 +34,25 @@ export default class AdminEditOrgCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
backendSrv.put('/api/orgs/' + $scope.org.id, $scope.org).then(() => {
|
||||
$location.path('/admin/orgs');
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.put('/api/orgs/' + $scope.org.id, $scope.org)
|
||||
.then(() => {
|
||||
$location.path('/admin/orgs');
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.updateOrgUser = (orgUser: any) => {
|
||||
backendSrv.patch('/api/orgs/' + orgUser.orgId + '/users/' + orgUser.userId, orgUser);
|
||||
getBackendSrv().patch('/api/orgs/' + orgUser.orgId + '/users/' + orgUser.userId, orgUser);
|
||||
};
|
||||
|
||||
$scope.removeOrgUser = (orgUser: any) => {
|
||||
backendSrv.delete('/api/orgs/' + orgUser.orgId + '/users/' + orgUser.userId).then(() => {
|
||||
$scope.getOrgUsers($scope.org.id);
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.delete('/api/orgs/' + orgUser.orgId + '/users/' + orgUser.userId)
|
||||
.then(() => $scope.getOrgUsers($scope.org.id))
|
||||
);
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
@ -1,19 +1,14 @@
|
||||
import _ from 'lodash';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { User } from 'app/core/services/context_srv';
|
||||
import { UserSession, Scope, CoreEvents, AppEventEmitter } from 'app/types';
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
|
||||
export default class AdminEditUserCtrl {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
$scope: Scope & AppEventEmitter,
|
||||
$routeParams: any,
|
||||
backendSrv: BackendSrv,
|
||||
$location: any,
|
||||
navModelSrv: NavModelSrv
|
||||
) {
|
||||
constructor($scope: Scope & AppEventEmitter, $routeParams: any, $location: any, navModelSrv: NavModelSrv) {
|
||||
$scope.user = {};
|
||||
$scope.sessions = [];
|
||||
$scope.newOrg = { name: '', role: 'Editor' };
|
||||
@ -22,60 +17,74 @@ export default class AdminEditUserCtrl {
|
||||
|
||||
$scope.init = () => {
|
||||
if ($routeParams.id) {
|
||||
$scope.getUser($routeParams.id);
|
||||
$scope.getUserSessions($routeParams.id);
|
||||
$scope.getUserOrgs($routeParams.id);
|
||||
promiseToDigest($scope)(
|
||||
Promise.all([
|
||||
$scope.getUser($routeParams.id),
|
||||
$scope.getUserSessions($routeParams.id),
|
||||
$scope.getUserOrgs($routeParams.id),
|
||||
])
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getUser = (id: number) => {
|
||||
backendSrv.get('/api/users/' + id).then((user: User) => {
|
||||
$scope.user = user;
|
||||
$scope.user_id = id;
|
||||
$scope.permissions.isGrafanaAdmin = user.isGrafanaAdmin;
|
||||
});
|
||||
return getBackendSrv()
|
||||
.get('/api/users/' + id)
|
||||
.then((user: User) => {
|
||||
$scope.user = user;
|
||||
$scope.user_id = id;
|
||||
$scope.permissions.isGrafanaAdmin = user.isGrafanaAdmin;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getUserSessions = (id: number) => {
|
||||
backendSrv.get('/api/admin/users/' + id + '/auth-tokens').then((sessions: UserSession[]) => {
|
||||
sessions.reverse();
|
||||
return getBackendSrv()
|
||||
.get('/api/admin/users/' + id + '/auth-tokens')
|
||||
.then((sessions: UserSession[]) => {
|
||||
sessions.reverse();
|
||||
|
||||
$scope.sessions = sessions.map((session: UserSession) => {
|
||||
return {
|
||||
id: session.id,
|
||||
isActive: session.isActive,
|
||||
seenAt: dateTime(session.seenAt).fromNow(),
|
||||
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
||||
clientIp: session.clientIp,
|
||||
browser: session.browser,
|
||||
browserVersion: session.browserVersion,
|
||||
os: session.os,
|
||||
osVersion: session.osVersion,
|
||||
device: session.device,
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.revokeUserSession = (tokenId: number) => {
|
||||
backendSrv
|
||||
.post('/api/admin/users/' + $scope.user_id + '/revoke-auth-token', {
|
||||
authTokenId: tokenId,
|
||||
})
|
||||
.then(() => {
|
||||
$scope.sessions = $scope.sessions.filter((session: UserSession) => {
|
||||
if (session.id === tokenId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
$scope.sessions = sessions.map((session: UserSession) => {
|
||||
return {
|
||||
id: session.id,
|
||||
isActive: session.isActive,
|
||||
seenAt: dateTime(session.seenAt).fromNow(),
|
||||
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
||||
clientIp: session.clientIp,
|
||||
browser: session.browser,
|
||||
browserVersion: session.browserVersion,
|
||||
os: session.os,
|
||||
osVersion: session.osVersion,
|
||||
device: session.device,
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.revokeUserSession = (tokenId: number) => {
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/admin/users/' + $scope.user_id + '/revoke-auth-token', {
|
||||
authTokenId: tokenId,
|
||||
})
|
||||
.then(() => {
|
||||
$scope.sessions = $scope.sessions.filter((session: UserSession) => {
|
||||
if (session.id === tokenId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.revokeAllUserSessions = (tokenId: number) => {
|
||||
backendSrv.post('/api/admin/users/' + $scope.user_id + '/logout').then(() => {
|
||||
$scope.sessions = [];
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/admin/users/' + $scope.user_id + '/logout')
|
||||
.then(() => {
|
||||
$scope.sessions = [];
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.setPassword = () => {
|
||||
@ -84,15 +93,19 @@ export default class AdminEditUserCtrl {
|
||||
}
|
||||
|
||||
const payload = { password: $scope.password };
|
||||
backendSrv.put('/api/admin/users/' + $scope.user_id + '/password', payload).then(() => {
|
||||
$location.path('/admin/users');
|
||||
});
|
||||
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.put('/api/admin/users/' + $scope.user_id + '/password', payload)
|
||||
.then(() => {
|
||||
$location.path('/admin/users');
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.updatePermissions = () => {
|
||||
const payload = $scope.permissions;
|
||||
|
||||
backendSrv.put('/api/admin/users/' + $scope.user_id + '/permissions', payload);
|
||||
getBackendSrv().put('/api/admin/users/' + $scope.user_id + '/permissions', payload);
|
||||
};
|
||||
|
||||
$scope.create = () => {
|
||||
@ -100,15 +113,21 @@ export default class AdminEditUserCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
backendSrv.post('/api/admin/users', $scope.user).then(() => {
|
||||
$location.path('/admin/users');
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/admin/users', $scope.user)
|
||||
.then(() => {
|
||||
$location.path('/admin/users');
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.getUserOrgs = (id: number) => {
|
||||
backendSrv.get('/api/users/' + id + '/orgs').then((orgs: any) => {
|
||||
$scope.orgs = orgs;
|
||||
});
|
||||
return getBackendSrv()
|
||||
.get('/api/users/' + id + '/orgs')
|
||||
.then((orgs: any) => {
|
||||
$scope.orgs = orgs;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.update = () => {
|
||||
@ -116,20 +135,27 @@ export default class AdminEditUserCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
backendSrv.put('/api/users/' + $scope.user_id, $scope.user).then(() => {
|
||||
$location.path('/admin/users');
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.put('/api/users/' + $scope.user_id, $scope.user)
|
||||
.then(() => {
|
||||
$location.path('/admin/users');
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.updateOrgUser = (orgUser: { orgId: string }) => {
|
||||
backendSrv.patch('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id, orgUser).then(() => {});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv().patch('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id, orgUser)
|
||||
);
|
||||
};
|
||||
|
||||
$scope.removeOrgUser = (orgUser: { orgId: string }) => {
|
||||
backendSrv.delete('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id).then(() => {
|
||||
$scope.getUser($scope.user_id);
|
||||
$scope.getUserOrgs($scope.user_id);
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.delete('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id)
|
||||
.then(() => Promise.all([$scope.getUser($scope.user_id), $scope.getUserOrgs($scope.user_id)]))
|
||||
);
|
||||
};
|
||||
|
||||
$scope.orgsSearchCache = [];
|
||||
@ -140,10 +166,14 @@ export default class AdminEditUserCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
backendSrv.get('/api/orgs', { query: '' }).then((result: any) => {
|
||||
$scope.orgsSearchCache = result;
|
||||
callback(_.map(result, 'name'));
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/orgs', { query: '' })
|
||||
.then((result: any) => {
|
||||
$scope.orgsSearchCache = result;
|
||||
callback(_.map(result, 'name'));
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.addOrgUser = () => {
|
||||
@ -161,10 +191,11 @@ export default class AdminEditUserCtrl {
|
||||
|
||||
$scope.newOrg.loginOrEmail = $scope.user.login;
|
||||
|
||||
backendSrv.post('/api/orgs/' + orgInfo.id + '/users/', $scope.newOrg).then(() => {
|
||||
$scope.getUser($scope.user_id);
|
||||
$scope.getUserOrgs($scope.user_id);
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/orgs/' + orgInfo.id + '/users/', $scope.newOrg)
|
||||
.then(() => Promise.all([$scope.getUser($scope.user_id), $scope.getUserOrgs($scope.user_id)]))
|
||||
);
|
||||
};
|
||||
|
||||
$scope.deleteUser = (user: any) => {
|
||||
@ -174,9 +205,13 @@ export default class AdminEditUserCtrl {
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
backendSrv.delete('/api/admin/users/' + user.id).then(() => {
|
||||
$location.path('/admin/users');
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.delete('/api/admin/users/' + user.id)
|
||||
.then(() => {
|
||||
$location.path('/admin/users');
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -192,9 +227,10 @@ export default class AdminEditUserCtrl {
|
||||
}
|
||||
|
||||
const actionEndpoint = user.isDisabled ? '/enable' : '/disable';
|
||||
backendSrv.post('/api/admin/users/' + user.id + actionEndpoint).then(() => {
|
||||
$scope.init();
|
||||
});
|
||||
|
||||
getBackendSrv()
|
||||
.post('/api/admin/users/' + user.id + actionEndpoint)
|
||||
.then(() => $scope.init());
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { Scope, CoreEvents, AppEventEmitter } from 'app/types';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
|
||||
export default class AdminListOrgsCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: Scope & AppEventEmitter, backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
$scope.init = () => {
|
||||
constructor($scope: Scope & AppEventEmitter, navModelSrv: NavModelSrv) {
|
||||
$scope.init = async () => {
|
||||
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
|
||||
$scope.getOrgs();
|
||||
await $scope.getOrgs();
|
||||
};
|
||||
|
||||
$scope.getOrgs = () => {
|
||||
backendSrv.get('/api/orgs').then((orgs: any) => {
|
||||
$scope.orgs = orgs;
|
||||
});
|
||||
$scope.getOrgs = async () => {
|
||||
const orgs = await promiseToDigest($scope)(getBackendSrv().get('/api/orgs'));
|
||||
$scope.orgs = orgs;
|
||||
};
|
||||
|
||||
$scope.deleteOrg = (org: any) => {
|
||||
@ -23,10 +23,9 @@ export default class AdminListOrgsCtrl {
|
||||
text2: 'All dashboards for this organization will be removed!',
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
backendSrv.delete('/api/orgs/' + org.id).then(() => {
|
||||
$scope.getOrgs();
|
||||
});
|
||||
onConfirm: async () => {
|
||||
await getBackendSrv().delete('/api/orgs/' + org.id);
|
||||
await $scope.getOrgs();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { getTagColorsFromName } from '@grafana/ui';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { Scope } from 'app/types/angular';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
|
||||
export default class AdminListUsersCtrl {
|
||||
users: any;
|
||||
@ -13,29 +15,31 @@ export default class AdminListUsersCtrl {
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
constructor(private $scope: Scope, navModelSrv: NavModelSrv) {
|
||||
this.navModel = navModelSrv.getNav('admin', 'global-users', 0);
|
||||
this.query = '';
|
||||
this.getUsers();
|
||||
}
|
||||
|
||||
getUsers() {
|
||||
this.backendSrv
|
||||
.get(`/api/users/search?perpage=${this.perPage}&page=${this.page}&query=${this.query}`)
|
||||
.then((result: any) => {
|
||||
this.users = result.users;
|
||||
this.page = result.page;
|
||||
this.perPage = result.perPage;
|
||||
this.totalPages = Math.ceil(result.totalCount / result.perPage);
|
||||
this.showPaging = this.totalPages > 1;
|
||||
this.pages = [];
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get(`/api/users/search?perpage=${this.perPage}&page=${this.page}&query=${this.query}`)
|
||||
.then((result: any) => {
|
||||
this.users = result.users;
|
||||
this.page = result.page;
|
||||
this.perPage = result.perPage;
|
||||
this.totalPages = Math.ceil(result.totalCount / result.perPage);
|
||||
this.showPaging = this.totalPages > 1;
|
||||
this.pages = [];
|
||||
|
||||
for (let i = 1; i < this.totalPages + 1; i++) {
|
||||
this.pages.push({ page: i, current: i === this.page });
|
||||
}
|
||||
for (let i = 1; i < this.totalPages + 1; i++) {
|
||||
this.pages.push({ page: i, current: i === this.page });
|
||||
}
|
||||
|
||||
this.addUsersAuthLabels();
|
||||
});
|
||||
this.addUsersAuthLabels();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
navigateToPage(page: any) {
|
||||
|
@ -9,8 +9,6 @@ import { StoreState } from 'app/types';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
|
||||
const backendSrv = getBackendSrv();
|
||||
|
||||
type Settings = { [key: string]: { [key: string]: string } };
|
||||
|
||||
interface Props {
|
||||
@ -29,7 +27,7 @@ export class AdminSettings extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
const settings: Settings = await backendSrv.get('/api/admin/settings');
|
||||
const settings: Settings = await getBackendSrv().get('/api/admin/settings');
|
||||
this.setState({
|
||||
settings,
|
||||
isLoading: false,
|
||||
|
@ -5,7 +5,7 @@ import { QueryPart } from 'app/core/components/query_part/query_part';
|
||||
import alertDef from './state/alertDef';
|
||||
import config from 'app/core/config';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { DashboardSrv } from '../dashboard/services/DashboardSrv';
|
||||
import DatasourceSrv from '../plugins/datasource_srv';
|
||||
import { DataQuery, DataSourceApi } from '@grafana/data';
|
||||
@ -13,6 +13,7 @@ import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { getDefaultCondition } from './getAlertingValidationMessage';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
|
||||
export class AlertTabCtrl {
|
||||
panel: PanelModel;
|
||||
@ -39,7 +40,6 @@ export class AlertTabCtrl {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $scope: any,
|
||||
private backendSrv: BackendSrv,
|
||||
private dashboardSrv: DashboardSrv,
|
||||
private uiSegmentSrv: any,
|
||||
private datasourceSrv: DatasourceSrv
|
||||
@ -78,25 +78,31 @@ export class AlertTabCtrl {
|
||||
this.alertNotifications = [];
|
||||
this.alertHistory = [];
|
||||
|
||||
return this.backendSrv.get('/api/alert-notifications/lookup').then((res: any) => {
|
||||
this.notifications = res;
|
||||
return promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/alert-notifications/lookup')
|
||||
.then((res: any) => {
|
||||
this.notifications = res;
|
||||
|
||||
this.initModel();
|
||||
this.validateModel();
|
||||
});
|
||||
this.initModel();
|
||||
this.validateModel();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getAlertHistory() {
|
||||
this.backendSrv
|
||||
.get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50&type=alert`)
|
||||
.then((res: any) => {
|
||||
this.alertHistory = _.map(res, ah => {
|
||||
ah.time = this.dashboardSrv.getCurrent().formatDate(ah.time, 'MMM D, YYYY HH:mm:ss');
|
||||
ah.stateModel = alertDef.getStateDisplayModel(ah.newState);
|
||||
ah.info = alertDef.getAlertAnnotationInfo(ah);
|
||||
return ah;
|
||||
});
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50&type=alert`)
|
||||
.then((res: any) => {
|
||||
this.alertHistory = _.map(res, ah => {
|
||||
ah.time = this.dashboardSrv.getCurrent().formatDate(ah.time, 'MMM D, YYYY HH:mm:ss');
|
||||
ah.stateModel = alertDef.getStateDisplayModel(ah.newState);
|
||||
ah.info = alertDef.getAlertAnnotationInfo(ah);
|
||||
return ah;
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getNotificationIcon(type: string): string {
|
||||
@ -459,15 +465,17 @@ export class AlertTabCtrl {
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Yes',
|
||||
onConfirm: () => {
|
||||
this.backendSrv
|
||||
.post('/api/annotations/mass-delete', {
|
||||
dashboardId: this.panelCtrl.dashboard.id,
|
||||
panelId: this.panel.id,
|
||||
})
|
||||
.then(() => {
|
||||
this.alertHistory = [];
|
||||
this.panelCtrl.refresh();
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/annotations/mass-delete', {
|
||||
dashboardId: this.panelCtrl.dashboard.id,
|
||||
panelId: this.panel.id,
|
||||
})
|
||||
.then(() => {
|
||||
this.alertHistory = [];
|
||||
this.panelCtrl.refresh();
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
import { appEvents, coreModule, NavModelSrv } from 'app/core/core';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { IScope } from 'angular';
|
||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||
|
||||
export class AlertNotificationEditCtrl {
|
||||
theForm: any;
|
||||
@ -28,8 +30,8 @@ export class AlertNotificationEditCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $scope: IScope,
|
||||
private $routeParams: any,
|
||||
private backendSrv: BackendSrv,
|
||||
private $location: any,
|
||||
private $templateCache: any,
|
||||
navModelSrv: NavModelSrv
|
||||
@ -41,33 +43,37 @@ export class AlertNotificationEditCtrl {
|
||||
return ['1m', '5m', '10m', '15m', '30m', '1h'];
|
||||
};
|
||||
|
||||
this.backendSrv
|
||||
.get(`/api/alert-notifiers`)
|
||||
.then((notifiers: any) => {
|
||||
this.notifiers = notifiers;
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get(`/api/alert-notifiers`)
|
||||
.then((notifiers: any) => {
|
||||
this.notifiers = notifiers;
|
||||
|
||||
// add option templates
|
||||
for (const notifier of this.notifiers) {
|
||||
this.$templateCache.put(this.getNotifierTemplateId(notifier.type), notifier.optionsTemplate);
|
||||
}
|
||||
// add option templates
|
||||
for (const notifier of this.notifiers) {
|
||||
this.$templateCache.put(this.getNotifierTemplateId(notifier.type), notifier.optionsTemplate);
|
||||
}
|
||||
|
||||
if (!this.$routeParams.id) {
|
||||
this.navModel.breadcrumbs.push({ text: 'New channel' });
|
||||
this.navModel.node = { text: 'New channel' };
|
||||
return _.defaults(this.model, this.defaults);
|
||||
}
|
||||
if (!this.$routeParams.id) {
|
||||
this.navModel.breadcrumbs.push({ text: 'New channel' });
|
||||
this.navModel.node = { text: 'New channel' };
|
||||
return _.defaults(this.model, this.defaults);
|
||||
}
|
||||
|
||||
return this.backendSrv.get(`/api/alert-notifications/${this.$routeParams.id}`).then((result: any) => {
|
||||
this.navModel.breadcrumbs.push({ text: result.name });
|
||||
this.navModel.node = { text: result.name };
|
||||
result.settings = _.defaults(result.settings, this.defaults.settings);
|
||||
return result;
|
||||
});
|
||||
})
|
||||
.then((model: any) => {
|
||||
this.model = model;
|
||||
this.notifierTemplateId = this.getNotifierTemplateId(this.model.type);
|
||||
});
|
||||
return getBackendSrv()
|
||||
.get(`/api/alert-notifications/${this.$routeParams.id}`)
|
||||
.then((result: any) => {
|
||||
this.navModel.breadcrumbs.push({ text: result.name });
|
||||
this.navModel.node = { text: result.name };
|
||||
result.settings = _.defaults(result.settings, this.defaults.settings);
|
||||
return result;
|
||||
});
|
||||
})
|
||||
.then((model: any) => {
|
||||
this.model = model;
|
||||
this.notifierTemplateId = this.getNotifierTemplateId(this.model.type);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
save() {
|
||||
@ -76,37 +82,45 @@ export class AlertNotificationEditCtrl {
|
||||
}
|
||||
|
||||
if (this.model.id) {
|
||||
this.backendSrv
|
||||
.put(`/api/alert-notifications/${this.model.id}`, this.model)
|
||||
.then((res: any) => {
|
||||
this.model = res;
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Notification updated']);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
if (err.data && err.data.error) {
|
||||
appEvents.emit(AppEvents.alertError, [err.data.error]);
|
||||
}
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.put(`/api/alert-notifications/${this.model.id}`, this.model)
|
||||
.then((res: any) => {
|
||||
this.model = res;
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Notification updated']);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
if (err.data && err.data.error) {
|
||||
appEvents.emit(AppEvents.alertError, [err.data.error]);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.backendSrv
|
||||
.post(`/api/alert-notifications`, this.model)
|
||||
.then((res: any) => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Notification created']);
|
||||
this.$location.path('alerting/notifications');
|
||||
})
|
||||
.catch((err: any) => {
|
||||
if (err.data && err.data.error) {
|
||||
appEvents.emit(AppEvents.alertError, [err.data.error]);
|
||||
}
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.post(`/api/alert-notifications`, this.model)
|
||||
.then((res: any) => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Notification created']);
|
||||
this.$location.path('alerting/notifications');
|
||||
})
|
||||
.catch((err: any) => {
|
||||
if (err.data && err.data.error) {
|
||||
appEvents.emit(AppEvents.alertError, [err.data.error]);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
deleteNotification() {
|
||||
this.backendSrv.delete(`/api/alert-notifications/${this.model.id}`).then((res: any) => {
|
||||
this.model = res;
|
||||
this.$location.path('alerting/notifications');
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.delete(`/api/alert-notifications/${this.model.id}`)
|
||||
.then((res: any) => {
|
||||
this.model = res;
|
||||
this.$location.path('alerting/notifications');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getNotifierTemplateId(type: string) {
|
||||
@ -130,7 +144,7 @@ export class AlertNotificationEditCtrl {
|
||||
settings: this.model.settings,
|
||||
};
|
||||
|
||||
this.backendSrv.post(`/api/alert-notifications/test`, payload);
|
||||
promiseToDigest(this.$scope)(getBackendSrv().post(`/api/alert-notifications/test`, payload));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,39 @@
|
||||
import { IScope } from 'angular';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { coreModule, NavModelSrv } from 'app/core/core';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||
|
||||
export class AlertNotificationsListCtrl {
|
||||
notifications: any;
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
constructor(private $scope: IScope, navModelSrv: NavModelSrv) {
|
||||
this.loadNotifications();
|
||||
this.navModel = navModelSrv.getNav('alerting', 'channels', 0);
|
||||
}
|
||||
|
||||
loadNotifications() {
|
||||
this.backendSrv.get(`/api/alert-notifications`).then((result: any) => {
|
||||
this.notifications = result;
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get(`/api/alert-notifications`)
|
||||
.then((result: any) => {
|
||||
this.notifications = result;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
deleteNotification(id: number) {
|
||||
this.backendSrv.delete(`/api/alert-notifications/${id}`).then(() => {
|
||||
this.notifications = this.notifications.filter((notification: any) => {
|
||||
return notification.id !== id;
|
||||
});
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.delete(`/api/alert-notifications/${id}`)
|
||||
.then(() => {
|
||||
this.notifications = this.notifications.filter((notification: any) => {
|
||||
return notification.id !== id;
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,16 @@ import { TestRuleResult, Props } from './TestRuleResult';
|
||||
import { DashboardModel } from '../dashboard/state';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
jest.mock('@grafana/runtime/src/services/backendSrv', () => ({
|
||||
getBackendSrv: () => ({
|
||||
post: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
const original = jest.requireActual('@grafana/runtime');
|
||||
|
||||
return {
|
||||
...original,
|
||||
getBackendSrv: () => ({
|
||||
post: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
|
@ -4,7 +4,7 @@ import { LoadingPlaceholder, JSONFormatter } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||
import { DashboardModel } from '../dashboard/state/DashboardModel';
|
||||
import { getBackendSrv, BackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
export interface Props {
|
||||
@ -27,12 +27,6 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
||||
|
||||
formattedJson: any;
|
||||
clipboard: any;
|
||||
backendSrv: BackendSrv = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.backendSrv = getBackendSrv();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.testRule();
|
||||
@ -43,7 +37,7 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
||||
const payload = { dashboard: dashboard.getSaveModelClone(), panelId };
|
||||
|
||||
this.setState({ isLoading: true });
|
||||
const testRuleResponse = await this.backendSrv.post(`/api/alerts/test`, payload);
|
||||
const testRuleResponse = await getBackendSrv().post(`/api/alerts/test`, payload);
|
||||
this.setState({ isLoading: false, testRuleResponse });
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Libaries
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
// Components
|
||||
import './editor_ctrl';
|
||||
@ -11,25 +11,16 @@ import { dedupAnnotations } from './events_processing';
|
||||
|
||||
// Types
|
||||
import { DashboardModel } from '../dashboard/state/DashboardModel';
|
||||
import DatasourceSrv from '../plugins/datasource_srv';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { TimeSrv } from '../dashboard/services/TimeSrv';
|
||||
import { DataSourceApi, PanelEvents, AnnotationEvent, AppEvents, PanelModel, TimeRange } from '@grafana/data';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { getBackendSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
import { getTimeSrv } from '../dashboard/services/TimeSrv';
|
||||
|
||||
export class AnnotationsSrv {
|
||||
globalAnnotationsPromise: any;
|
||||
alertStatesPromise: any;
|
||||
datasourcePromises: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $rootScope: GrafanaRootScope,
|
||||
private datasourceSrv: DatasourceSrv,
|
||||
private backendSrv: BackendSrv,
|
||||
private timeSrv: TimeSrv
|
||||
) {}
|
||||
|
||||
init(dashboard: DashboardModel) {
|
||||
// always clearPromiseCaches when loading new dashboard
|
||||
this.clearPromiseCaches();
|
||||
@ -47,10 +38,10 @@ export class AnnotationsSrv {
|
||||
return Promise.all([this.getGlobalAnnotations(options), this.getAlertStates(options)])
|
||||
.then(results => {
|
||||
// combine the annotations and flatten results
|
||||
let annotations: AnnotationEvent[] = _.flattenDeep(results[0]);
|
||||
let annotations: AnnotationEvent[] = flattenDeep(results[0]);
|
||||
|
||||
// filter out annotations that do not belong to requesting panel
|
||||
annotations = _.filter(annotations, item => {
|
||||
annotations = annotations.filter(item => {
|
||||
// if event has panel id and query is of type dashboard then panel and requesting panel id must match
|
||||
if (item.panelId && item.source.type === 'dashboard') {
|
||||
return item.panelId === options.panel.id;
|
||||
@ -61,7 +52,7 @@ export class AnnotationsSrv {
|
||||
annotations = dedupAnnotations(annotations);
|
||||
|
||||
// look for alert state for this panel
|
||||
const alertState: any = _.find(results[1], { panelId: options.panel.id });
|
||||
const alertState: any = results[1].find((res: any) => res.panelId === options.panel.id);
|
||||
|
||||
return {
|
||||
annotations: annotations,
|
||||
@ -73,7 +64,7 @@ export class AnnotationsSrv {
|
||||
err.message = err.data.message;
|
||||
}
|
||||
console.log('AnnotationSrv.query error', err);
|
||||
this.$rootScope.appEvent(AppEvents.alertError, ['Annotation Query Failed', err.message || err]);
|
||||
appEvents.emit(AppEvents.alertError, ['Annotation Query Failed', err.message || err]);
|
||||
return [];
|
||||
});
|
||||
}
|
||||
@ -96,7 +87,7 @@ export class AnnotationsSrv {
|
||||
return this.alertStatesPromise;
|
||||
}
|
||||
|
||||
this.alertStatesPromise = this.backendSrv.get('/api/alerts/states-for-dashboard', {
|
||||
this.alertStatesPromise = getBackendSrv().get('/api/alerts/states-for-dashboard', {
|
||||
dashboardId: options.dashboard.id,
|
||||
});
|
||||
return this.alertStatesPromise;
|
||||
@ -109,7 +100,7 @@ export class AnnotationsSrv {
|
||||
return this.globalAnnotationsPromise;
|
||||
}
|
||||
|
||||
const range = this.timeSrv.timeRange();
|
||||
const range = getTimeSrv().timeRange();
|
||||
const promises = [];
|
||||
const dsPromises = [];
|
||||
|
||||
@ -121,7 +112,7 @@ export class AnnotationsSrv {
|
||||
if (annotation.snapshotData) {
|
||||
return this.translateQueryResult(annotation, annotation.snapshotData);
|
||||
}
|
||||
const datasourcePromise = this.datasourceSrv.get(annotation.datasource);
|
||||
const datasourcePromise = getDataSourceSrv().get(annotation.datasource);
|
||||
dsPromises.push(datasourcePromise);
|
||||
promises.push(
|
||||
datasourcePromise
|
||||
@ -137,7 +128,7 @@ export class AnnotationsSrv {
|
||||
.then(results => {
|
||||
// store response in annotation object if this is a snapshot call
|
||||
if (dashboard.snapshot) {
|
||||
annotation.snapshotData = angular.copy(results);
|
||||
annotation.snapshotData = cloneDeep(results);
|
||||
}
|
||||
// translate result
|
||||
return this.translateQueryResult(annotation, results);
|
||||
@ -151,26 +142,26 @@ export class AnnotationsSrv {
|
||||
|
||||
saveAnnotationEvent(annotation: AnnotationEvent) {
|
||||
this.globalAnnotationsPromise = null;
|
||||
return this.backendSrv.post('/api/annotations', annotation);
|
||||
return getBackendSrv().post('/api/annotations', annotation);
|
||||
}
|
||||
|
||||
updateAnnotationEvent(annotation: AnnotationEvent) {
|
||||
this.globalAnnotationsPromise = null;
|
||||
return this.backendSrv.put(`/api/annotations/${annotation.id}`, annotation);
|
||||
return getBackendSrv().put(`/api/annotations/${annotation.id}`, annotation);
|
||||
}
|
||||
|
||||
deleteAnnotationEvent(annotation: AnnotationEvent) {
|
||||
this.globalAnnotationsPromise = null;
|
||||
const deleteUrl = `/api/annotations/${annotation.id}`;
|
||||
|
||||
return this.backendSrv.delete(deleteUrl);
|
||||
return getBackendSrv().delete(deleteUrl);
|
||||
}
|
||||
|
||||
translateQueryResult(annotation: any, results: any) {
|
||||
// if annotation has snapshotData
|
||||
// make clone and remove it
|
||||
if (annotation.snapshotData) {
|
||||
annotation = angular.copy(annotation);
|
||||
annotation = cloneDeep(annotation);
|
||||
delete annotation.snapshotData;
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,7 @@
|
||||
import { AnnotationsSrv } from '../annotations_srv';
|
||||
|
||||
describe('AnnotationsSrv', () => {
|
||||
const $rootScope: any = {
|
||||
onAppEvent: jest.fn(),
|
||||
};
|
||||
|
||||
const annotationsSrv = new AnnotationsSrv($rootScope, null, null, null);
|
||||
const annotationsSrv = new AnnotationsSrv();
|
||||
|
||||
describe('When translating the query result', () => {
|
||||
const annotationSource = {
|
||||
|
@ -25,7 +25,7 @@ export function loadApiKeys(includeExpired: boolean): ThunkResult<void> {
|
||||
export function deleteApiKey(id: number, includeExpired: boolean): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
getBackendSrv()
|
||||
.delete('/api/auth/keys/' + id)
|
||||
.then(dispatch(loadApiKeys(includeExpired)));
|
||||
.delete(`/api/auth/keys/${id}`)
|
||||
.then(() => dispatch(loadApiKeys(includeExpired)));
|
||||
};
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import { iconMap } from './DashLinksEditorCtrl';
|
||||
import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSrv } from '../../services/DashboardSrv';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||
|
||||
export type DashboardLink = { tags: any; target: string; keepTime: any; includeVars: any };
|
||||
|
||||
@ -94,13 +95,7 @@ function dashLink($compile: any, $sanitize: any, linkSrv: LinkSrv) {
|
||||
|
||||
export class DashLinksContainerCtrl {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
$scope: any,
|
||||
$rootScope: GrafanaRootScope,
|
||||
backendSrv: BackendSrv,
|
||||
dashboardSrv: DashboardSrv,
|
||||
linkSrv: LinkSrv
|
||||
) {
|
||||
constructor($scope: any, $rootScope: GrafanaRootScope, dashboardSrv: DashboardSrv, linkSrv: LinkSrv) {
|
||||
const currentDashId = dashboardSrv.getCurrent().id;
|
||||
|
||||
function buildLinks(linkDef: any) {
|
||||
@ -154,26 +149,28 @@ export class DashLinksContainerCtrl {
|
||||
}
|
||||
|
||||
$scope.searchDashboards = (link: DashboardLink, limit: any) => {
|
||||
return backendSrv.search({ tag: link.tags, limit: limit }).then(results => {
|
||||
return _.reduce(
|
||||
results,
|
||||
(memo, dash) => {
|
||||
// do not add current dashboard
|
||||
if (dash.id !== currentDashId) {
|
||||
memo.push({
|
||||
title: dash.title,
|
||||
url: dash.url,
|
||||
target: link.target === '_self' ? '' : link.target,
|
||||
icon: 'fa fa-th-large',
|
||||
keepTime: link.keepTime,
|
||||
includeVars: link.includeVars,
|
||||
});
|
||||
}
|
||||
return memo;
|
||||
},
|
||||
[]
|
||||
);
|
||||
});
|
||||
return promiseToDigest($scope)(
|
||||
backendSrv.search({ tag: link.tags, limit: limit }).then(results => {
|
||||
return _.reduce(
|
||||
results,
|
||||
(memo, dash) => {
|
||||
// do not add current dashboard
|
||||
if (dash.id !== currentDashId) {
|
||||
memo.push({
|
||||
title: dash.title,
|
||||
url: dash.url,
|
||||
target: link.target === '_self' ? '' : link.target,
|
||||
icon: 'fa fa-th-large',
|
||||
keepTime: link.keepTime,
|
||||
includeVars: link.includeVars,
|
||||
});
|
||||
}
|
||||
return memo;
|
||||
},
|
||||
[]
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.fillDropdown = (link: { searchHits: any }) => {
|
||||
|
@ -1,15 +1,17 @@
|
||||
import { appEvents, contextSrv, coreModule } from 'app/core/core';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import angular, { ILocationService } from 'angular';
|
||||
import angular, { ILocationService, IScope } from 'angular';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
import { appEvents, contextSrv, coreModule } from 'app/core/core';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import config from 'app/core/config';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSrv } from '../../services/DashboardSrv';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||
|
||||
export class SettingsCtrl {
|
||||
dashboard: DashboardModel;
|
||||
@ -26,11 +28,10 @@ export class SettingsCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $scope: any,
|
||||
private $scope: IScope & Record<string, any>,
|
||||
private $route: any,
|
||||
private $location: ILocationService,
|
||||
private $rootScope: GrafanaRootScope,
|
||||
private backendSrv: BackendSrv,
|
||||
private dashboardSrv: DashboardSrv
|
||||
) {
|
||||
// temp hack for annotations and variables editors
|
||||
@ -234,10 +235,12 @@ export class SettingsCtrl {
|
||||
}
|
||||
|
||||
deleteDashboardConfirmed() {
|
||||
this.backendSrv.deleteDashboard(this.dashboard.uid, false).then(() => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
|
||||
this.$location.url('/');
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
backendSrv.deleteDashboard(this.dashboard.uid, false).then(() => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
|
||||
this.$location.url('/');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
onFolderChange(folder: { id: number; title: string }) {
|
||||
|
@ -1,10 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import { IScope } from 'angular';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { ValidationSrv } from 'app/features/manage-dashboards';
|
||||
import { ContextSrv } from 'app/core/services/context_srv';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||
|
||||
export class FolderPickerCtrl {
|
||||
initialTitle: string;
|
||||
@ -28,7 +31,7 @@ export class FolderPickerCtrl {
|
||||
dashboardId?: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv, private validationSrv: ValidationSrv, private contextSrv: ContextSrv) {
|
||||
constructor(private validationSrv: ValidationSrv, private contextSrv: ContextSrv, private $scope: IScope) {
|
||||
this.isEditor = this.contextSrv.isEditor;
|
||||
|
||||
if (!this.labelClass) {
|
||||
@ -45,33 +48,35 @@ export class FolderPickerCtrl {
|
||||
permission: 'Edit',
|
||||
};
|
||||
|
||||
return this.backendSrv.get('api/search', params).then((result: any) => {
|
||||
if (
|
||||
this.isEditor &&
|
||||
(query === '' ||
|
||||
query.toLowerCase() === 'g' ||
|
||||
query.toLowerCase() === 'ge' ||
|
||||
query.toLowerCase() === 'gen' ||
|
||||
query.toLowerCase() === 'gene' ||
|
||||
query.toLowerCase() === 'gener' ||
|
||||
query.toLowerCase() === 'genera' ||
|
||||
query.toLowerCase() === 'general')
|
||||
) {
|
||||
result.unshift({ title: this.rootName, id: 0 });
|
||||
}
|
||||
return promiseToDigest(this.$scope)(
|
||||
backendSrv.get('api/search', params).then((result: any) => {
|
||||
if (
|
||||
this.isEditor &&
|
||||
(query === '' ||
|
||||
query.toLowerCase() === 'g' ||
|
||||
query.toLowerCase() === 'ge' ||
|
||||
query.toLowerCase() === 'gen' ||
|
||||
query.toLowerCase() === 'gene' ||
|
||||
query.toLowerCase() === 'gener' ||
|
||||
query.toLowerCase() === 'genera' ||
|
||||
query.toLowerCase() === 'general')
|
||||
) {
|
||||
result.unshift({ title: this.rootName, id: 0 });
|
||||
}
|
||||
|
||||
if (this.isEditor && this.enableCreateNew && query === '') {
|
||||
result.unshift({ title: '-- New Folder --', id: -1 });
|
||||
}
|
||||
if (this.isEditor && this.enableCreateNew && query === '') {
|
||||
result.unshift({ title: '-- New Folder --', id: -1 });
|
||||
}
|
||||
|
||||
if (this.enableReset && query === '' && this.initialTitle !== '') {
|
||||
result.unshift({ title: this.initialTitle, id: null });
|
||||
}
|
||||
if (this.enableReset && query === '' && this.initialTitle !== '') {
|
||||
result.unshift({ title: this.initialTitle, id: null });
|
||||
}
|
||||
|
||||
return _.map(result, item => {
|
||||
return { text: item.title, value: item.id };
|
||||
});
|
||||
});
|
||||
return _.map(result, item => {
|
||||
return { text: item.title, value: item.id };
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
onFolderChange(option: { value: number; text: string }) {
|
||||
@ -105,13 +110,15 @@ export class FolderPickerCtrl {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
return this.backendSrv.createFolder({ title: this.newFolderName }).then((result: { title: string; id: number }) => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
|
||||
return promiseToDigest(this.$scope)(
|
||||
backendSrv.createFolder({ title: this.newFolderName }).then((result: { title: string; id: number }) => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
|
||||
|
||||
this.closeCreateFolder();
|
||||
this.folder = { text: result.title, value: result.id };
|
||||
this.onFolderChange(this.folder);
|
||||
});
|
||||
this.closeCreateFolder();
|
||||
this.folder = { text: result.title, value: result.id };
|
||||
this.onFolderChange(this.folder);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
cancelCreateFolder(evt: any) {
|
||||
|
@ -96,7 +96,7 @@ export class SaveDashboardModalCtrl {
|
||||
this.selectors = e2e.pages.SaveDashboardModal.selectors;
|
||||
}
|
||||
|
||||
save() {
|
||||
save(): void | Promise<any> {
|
||||
if (!this.saveForm.$valid) {
|
||||
return;
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
import angular, { ILocationService } from 'angular';
|
||||
import angular, { ILocationService, IScope } from 'angular';
|
||||
import _ from 'lodash';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { TimeSrv } from '../../services/TimeSrv';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { PanelModel } from '../../state/PanelModel';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||
|
||||
export class ShareSnapshotCtrl {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
$scope: any,
|
||||
$scope: IScope & Record<string, any>,
|
||||
$rootScope: GrafanaRootScope,
|
||||
$location: ILocationService,
|
||||
backendSrv: BackendSrv,
|
||||
$timeout: any,
|
||||
timeSrv: TimeSrv
|
||||
) {
|
||||
@ -38,10 +39,14 @@ export class ShareSnapshotCtrl {
|
||||
];
|
||||
|
||||
$scope.init = () => {
|
||||
backendSrv.get('/api/snapshot/shared-options').then((options: { [x: string]: any }) => {
|
||||
$scope.sharingButtonText = options['externalSnapshotName'];
|
||||
$scope.externalEnabled = options['externalEnabled'];
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/snapshot/shared-options')
|
||||
.then((options: { [x: string]: any }) => {
|
||||
$scope.sharingButtonText = options['externalSnapshotName'];
|
||||
$scope.externalEnabled = options['externalEnabled'];
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.apiUrl = '/api/snapshots';
|
||||
@ -75,16 +80,20 @@ export class ShareSnapshotCtrl {
|
||||
external: external,
|
||||
};
|
||||
|
||||
backendSrv.post($scope.apiUrl, cmdData).then(
|
||||
(results: { deleteUrl: any; url: any }) => {
|
||||
$scope.loading = false;
|
||||
$scope.deleteUrl = results.deleteUrl;
|
||||
$scope.snapshotUrl = results.url;
|
||||
$scope.step = 2;
|
||||
},
|
||||
() => {
|
||||
$scope.loading = false;
|
||||
}
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.post($scope.apiUrl, cmdData)
|
||||
.then(
|
||||
(results: { deleteUrl: any; url: any }) => {
|
||||
$scope.loading = false;
|
||||
$scope.deleteUrl = results.deleteUrl;
|
||||
$scope.snapshotUrl = results.url;
|
||||
$scope.step = 2;
|
||||
},
|
||||
() => {
|
||||
$scope.loading = false;
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@ -152,9 +161,13 @@ export class ShareSnapshotCtrl {
|
||||
};
|
||||
|
||||
$scope.deleteSnapshot = () => {
|
||||
backendSrv.get($scope.deleteUrl).then(() => {
|
||||
$scope.step = 3;
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.get($scope.deleteUrl)
|
||||
.then(() => {
|
||||
$scope.step = 3;
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import { IScope } from 'angular';
|
||||
|
||||
import { HistoryListCtrl } from './HistoryListCtrl';
|
||||
import { versions, compare, restore } from './__mocks__/history';
|
||||
import { compare, restore, versions } from './__mocks__/history';
|
||||
import { CoreEvents } from 'app/types';
|
||||
|
||||
describe('HistoryListCtrl', () => {
|
||||
@ -12,6 +14,7 @@ describe('HistoryListCtrl', () => {
|
||||
|
||||
let historySrv: any;
|
||||
let $rootScope: any;
|
||||
const $scope: IScope = ({ $evalAsync: jest.fn() } as any) as IScope;
|
||||
let historyListCtrl: any;
|
||||
beforeEach(() => {
|
||||
historySrv = {
|
||||
@ -28,7 +31,7 @@ describe('HistoryListCtrl', () => {
|
||||
beforeEach(() => {
|
||||
historySrv.getHistoryList = jest.fn(() => Promise.resolve({}));
|
||||
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, {});
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, $scope);
|
||||
|
||||
historyListCtrl.dashboard = {
|
||||
id: 2,
|
||||
@ -84,7 +87,7 @@ describe('HistoryListCtrl', () => {
|
||||
beforeEach(async () => {
|
||||
historySrv.getHistoryList = jest.fn(() => Promise.reject(new Error('HistoryListError')));
|
||||
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, {});
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, $scope);
|
||||
|
||||
await historyListCtrl.getLog();
|
||||
});
|
||||
@ -127,7 +130,7 @@ describe('HistoryListCtrl', () => {
|
||||
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
||||
historySrv.calculateDiff = jest.fn(() => Promise.resolve({}));
|
||||
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, {});
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, $scope);
|
||||
|
||||
historyListCtrl.dashboard = {
|
||||
id: 2,
|
||||
@ -260,7 +263,7 @@ describe('HistoryListCtrl', () => {
|
||||
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
||||
historySrv.restoreDashboard = jest.fn(() => Promise.resolve());
|
||||
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, {});
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, $scope);
|
||||
|
||||
historyListCtrl.dashboard = {
|
||||
id: 1,
|
||||
@ -279,7 +282,7 @@ describe('HistoryListCtrl', () => {
|
||||
beforeEach(async () => {
|
||||
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
||||
historySrv.restoreDashboard = jest.fn(() => Promise.resolve());
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, {});
|
||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, $scope);
|
||||
historySrv.restoreDashboard = jest.fn(() => Promise.reject(new Error('RestoreError')));
|
||||
historyListCtrl.restoreConfirm(RESTORE_ID);
|
||||
await historyListCtrl.getLog();
|
||||
|
@ -1,12 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import angular, { ILocationService } from 'angular';
|
||||
import angular, { ILocationService, IScope } from 'angular';
|
||||
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './HistorySrv';
|
||||
import { dateTime, toUtc, DateTimeInput, AppEvents } from '@grafana/data';
|
||||
import { CalculateDiffOptions, HistoryListOpts, HistorySrv, RevisionsModel } from './HistorySrv';
|
||||
import { AppEvents, dateTime, DateTimeInput, toUtc } from '@grafana/data';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||
|
||||
export class HistoryListCtrl {
|
||||
appending: boolean;
|
||||
@ -30,7 +31,7 @@ export class HistoryListCtrl {
|
||||
private $rootScope: GrafanaRootScope,
|
||||
private $location: ILocationService,
|
||||
private historySrv: HistorySrv,
|
||||
public $scope: any
|
||||
public $scope: IScope
|
||||
) {
|
||||
this.appending = false;
|
||||
this.diff = 'basic';
|
||||
@ -108,18 +109,20 @@ export class HistoryListCtrl {
|
||||
diffType: diff,
|
||||
};
|
||||
|
||||
return this.historySrv
|
||||
.calculateDiff(options)
|
||||
.then((response: any) => {
|
||||
// @ts-ignore
|
||||
this.delta[this.diff] = response;
|
||||
})
|
||||
.catch(() => {
|
||||
this.mode = 'list';
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
return promiseToDigest(this.$scope)(
|
||||
this.historySrv
|
||||
.calculateDiff(options)
|
||||
.then((response: any) => {
|
||||
// @ts-ignore
|
||||
this.delta[this.diff] = response;
|
||||
})
|
||||
.catch(() => {
|
||||
this.mode = 'list';
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getLog(append = false) {
|
||||
@ -130,25 +133,27 @@ export class HistoryListCtrl {
|
||||
start: this.start,
|
||||
};
|
||||
|
||||
return this.historySrv
|
||||
.getHistoryList(this.dashboard, options)
|
||||
.then((revisions: any) => {
|
||||
// set formatted dates & default values
|
||||
for (const rev of revisions) {
|
||||
rev.createdDateString = this.formatDate(rev.created);
|
||||
rev.ageString = this.formatBasicDate(rev.created);
|
||||
rev.checked = false;
|
||||
}
|
||||
return promiseToDigest(this.$scope)(
|
||||
this.historySrv
|
||||
.getHistoryList(this.dashboard, options)
|
||||
.then((revisions: any) => {
|
||||
// set formatted dates & default values
|
||||
for (const rev of revisions) {
|
||||
rev.createdDateString = this.formatDate(rev.created);
|
||||
rev.ageString = this.formatBasicDate(rev.created);
|
||||
rev.checked = false;
|
||||
}
|
||||
|
||||
this.revisions = append ? this.revisions.concat(revisions) : revisions;
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.loading = false;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
this.appending = false;
|
||||
});
|
||||
this.revisions = append ? this.revisions.concat(revisions) : revisions;
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.loading = false;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
this.appending = false;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
isLastPage() {
|
||||
@ -183,17 +188,19 @@ export class HistoryListCtrl {
|
||||
|
||||
restoreConfirm(version: number) {
|
||||
this.loading = true;
|
||||
return this.historySrv
|
||||
.restoreDashboard(this.dashboard, version)
|
||||
.then((response: any) => {
|
||||
this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace();
|
||||
this.$route.reload();
|
||||
this.$rootScope.appEvent(AppEvents.alertSuccess, ['Dashboard restored', 'Restored from version ' + version]);
|
||||
})
|
||||
.catch(() => {
|
||||
this.mode = 'list';
|
||||
this.loading = false;
|
||||
});
|
||||
return promiseToDigest(this.$scope)(
|
||||
this.historySrv
|
||||
.restoreDashboard(this.dashboard, version)
|
||||
.then((response: any) => {
|
||||
this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace();
|
||||
this.$route.reload();
|
||||
this.$rootScope.appEvent(AppEvents.alertSuccess, ['Dashboard restored', 'Restored from version ' + version]);
|
||||
})
|
||||
.catch(() => {
|
||||
this.mode = 'list';
|
||||
this.loading = false;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,27 +1,41 @@
|
||||
import { versions, restore } from './__mocks__/history';
|
||||
import { HistorySrv } from './HistorySrv';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
|
||||
const getMock = jest.fn().mockResolvedValue({});
|
||||
const postMock = jest.fn().mockResolvedValue({});
|
||||
|
||||
jest.mock('app/core/store');
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
const original = jest.requireActual('@grafana/runtime');
|
||||
|
||||
return {
|
||||
...original,
|
||||
getBackendSrv: () => ({
|
||||
post: postMock,
|
||||
get: getMock,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('historySrv', () => {
|
||||
const versionsResponse = versions();
|
||||
const restoreResponse = restore;
|
||||
|
||||
const backendSrv: any = {
|
||||
get: jest.fn(() => Promise.resolve({})),
|
||||
post: jest.fn(() => Promise.resolve({})),
|
||||
};
|
||||
|
||||
let historySrv = new HistorySrv(backendSrv);
|
||||
let historySrv = new HistorySrv();
|
||||
|
||||
const dash = new DashboardModel({ id: 1 });
|
||||
const emptyDash = new DashboardModel({});
|
||||
const historyListOpts = { limit: 10, start: 0 };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getHistoryList', () => {
|
||||
it('should return a versions array for the given dashboard id', () => {
|
||||
backendSrv.get = jest.fn(() => Promise.resolve(versionsResponse));
|
||||
historySrv = new HistorySrv(backendSrv);
|
||||
getMock.mockImplementation(() => Promise.resolve(versionsResponse));
|
||||
historySrv = new HistorySrv();
|
||||
|
||||
return historySrv.getHistoryList(dash, historyListOpts).then((versions: any) => {
|
||||
expect(versions).toEqual(versionsResponse);
|
||||
@ -44,15 +58,15 @@ describe('historySrv', () => {
|
||||
describe('restoreDashboard', () => {
|
||||
it('should return a success response given valid parameters', () => {
|
||||
const version = 6;
|
||||
backendSrv.post = jest.fn(() => Promise.resolve(restoreResponse(version)));
|
||||
historySrv = new HistorySrv(backendSrv);
|
||||
postMock.mockImplementation(() => Promise.resolve(restoreResponse(version)));
|
||||
historySrv = new HistorySrv();
|
||||
return historySrv.restoreDashboard(dash, version).then((response: any) => {
|
||||
expect(response).toEqual(restoreResponse(version));
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an empty object when not given an id', async () => {
|
||||
historySrv = new HistorySrv(backendSrv);
|
||||
historySrv = new HistorySrv();
|
||||
const rsp = await historySrv.restoreDashboard(emptyDash, 6);
|
||||
expect(rsp).toEqual({});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
export interface HistoryListOpts {
|
||||
limit: number;
|
||||
@ -32,23 +32,20 @@ export interface DiffTarget {
|
||||
}
|
||||
|
||||
export class HistorySrv {
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv) {}
|
||||
|
||||
getHistoryList(dashboard: DashboardModel, options: HistoryListOpts) {
|
||||
const id = dashboard && dashboard.id ? dashboard.id : void 0;
|
||||
return id ? this.backendSrv.get(`api/dashboards/id/${id}/versions`, options) : Promise.resolve([]);
|
||||
return id ? getBackendSrv().get(`api/dashboards/id/${id}/versions`, options) : Promise.resolve([]);
|
||||
}
|
||||
|
||||
calculateDiff(options: CalculateDiffOptions) {
|
||||
return this.backendSrv.post('api/dashboards/calculate-diff', options);
|
||||
return getBackendSrv().post('api/dashboards/calculate-diff', options);
|
||||
}
|
||||
|
||||
restoreDashboard(dashboard: DashboardModel, version: number) {
|
||||
const id = dashboard && dashboard.id ? dashboard.id : void 0;
|
||||
const url = `api/dashboards/id/${id}/restore`;
|
||||
|
||||
return id && _.isNumber(version) ? this.backendSrv.post(url, { version }) : Promise.resolve({});
|
||||
return id && _.isNumber(version) ? getBackendSrv().post(url, { version }) : Promise.resolve({});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import { PanelOptionsGroup, TransformationsEditor, AlphaNotice } from '@grafana/
|
||||
import { QueryEditorRows } from './QueryEditorRows';
|
||||
// Services
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import config from 'app/core/config';
|
||||
// Types
|
||||
import { PanelModel } from '../state/PanelModel';
|
||||
@ -48,7 +48,7 @@ interface State {
|
||||
|
||||
export class QueriesTab extends PureComponent<Props, State> {
|
||||
datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
|
||||
backendSrv = getBackendSrv();
|
||||
backendSrv = backendSrv;
|
||||
querySubscription: Unsubscribable;
|
||||
|
||||
state: State = {
|
||||
|
@ -6,7 +6,7 @@ import $ from 'jquery';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { dateMath, AppEvents } from '@grafana/data';
|
||||
import impressionSrv from 'app/core/services/impression_srv';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSrv } from './DashboardSrv';
|
||||
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||
import { UrlQueryValue } from '@grafana/runtime';
|
||||
@ -15,7 +15,6 @@ import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
export class DashboardLoaderSrv {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private backendSrv: BackendSrv,
|
||||
private dashboardSrv: DashboardSrv,
|
||||
private datasourceSrv: DatasourceSrv,
|
||||
private $http: any,
|
||||
@ -46,11 +45,11 @@ export class DashboardLoaderSrv {
|
||||
if (type === 'script') {
|
||||
promise = this._loadScriptedDashboard(slug);
|
||||
} else if (type === 'snapshot') {
|
||||
promise = this.backendSrv.get('/api/snapshots/' + slug).catch(() => {
|
||||
promise = backendSrv.get('/api/snapshots/' + slug).catch(() => {
|
||||
return this._dashboardLoadFailed('Snapshot not found', true);
|
||||
});
|
||||
} else {
|
||||
promise = this.backendSrv
|
||||
promise = backendSrv
|
||||
.getDashboardByUid(uid)
|
||||
.then((result: any) => {
|
||||
if (result.meta.isFolder) {
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { ILocationService } from 'angular';
|
||||
import { AppEvents, PanelEvents } from '@grafana/data';
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { DashboardModel } from '../state/DashboardModel';
|
||||
import { removePanel } from '../utils/panel';
|
||||
import { DashboardMeta, CoreEvents } from 'app/types';
|
||||
import { CoreEvents, DashboardMeta } from 'app/types';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { ILocationService } from 'angular';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { promiseToDigest } from '../../../core/utils/promiseToDigest';
|
||||
|
||||
interface DashboardSaveOptions {
|
||||
folderId?: number;
|
||||
@ -21,11 +22,7 @@ export class DashboardSrv {
|
||||
dashboard: DashboardModel;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private backendSrv: BackendSrv,
|
||||
private $rootScope: GrafanaRootScope,
|
||||
private $location: ILocationService
|
||||
) {
|
||||
constructor(private $rootScope: GrafanaRootScope, private $location: ILocationService) {
|
||||
appEvents.on(CoreEvents.saveDashboard, this.saveDashboard.bind(this), $rootScope);
|
||||
appEvents.on(PanelEvents.panelChangeView, this.onPanelChangeView);
|
||||
appEvents.on(CoreEvents.removePanel, this.onRemovePanel);
|
||||
@ -167,10 +164,12 @@ export class DashboardSrv {
|
||||
save(clone: any, options?: DashboardSaveOptions) {
|
||||
options.folderId = options.folderId >= 0 ? options.folderId : this.dashboard.meta.folderId || clone.folderId;
|
||||
|
||||
return this.backendSrv
|
||||
.saveDashboard(clone, options)
|
||||
.then((data: any) => this.postSave(data))
|
||||
.catch(this.handleSaveDashboardError.bind(this, clone, { folderId: options.folderId }));
|
||||
return promiseToDigest(this.$rootScope)(
|
||||
backendSrv
|
||||
.saveDashboard(clone, options)
|
||||
.then((data: any) => this.postSave(data))
|
||||
.catch(this.handleSaveDashboardError.bind(this, clone, { folderId: options.folderId }))
|
||||
);
|
||||
}
|
||||
|
||||
saveDashboard(
|
||||
@ -228,13 +227,17 @@ export class DashboardSrv {
|
||||
let promise;
|
||||
|
||||
if (isStarred) {
|
||||
promise = this.backendSrv.delete('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
||||
return false;
|
||||
});
|
||||
promise = promiseToDigest(this.$rootScope)(
|
||||
backendSrv.delete('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
||||
return false;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
promise = this.backendSrv.post('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
||||
return true;
|
||||
});
|
||||
promise = promiseToDigest(this.$rootScope)(
|
||||
backendSrv.post('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return promise.then((res: boolean) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Services & Utils
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { DashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
@ -38,7 +38,7 @@ export interface InitDashboardArgs {
|
||||
}
|
||||
|
||||
async function redirectToNewUrl(slug: string, dispatch: ThunkDispatch, currentPath: string) {
|
||||
const res = await getBackendSrv().getDashboardBySlug(slug);
|
||||
const res = await backendSrv.getDashboardBySlug(slug);
|
||||
|
||||
if (res) {
|
||||
let newUrl = res.meta.url;
|
||||
@ -62,7 +62,7 @@ async function fetchDashboard(
|
||||
switch (args.routeInfo) {
|
||||
case DashboardRouteInfo.Home: {
|
||||
// load home dash
|
||||
const dashDTO: DashboardDTO = await getBackendSrv().get('/api/dashboards/home');
|
||||
const dashDTO: DashboardDTO = await backendSrv.get('/api/dashboards/home');
|
||||
|
||||
// if user specified a custom home dashboard redirect to that
|
||||
if (dashDTO.redirectUri) {
|
||||
|
@ -3,7 +3,7 @@ import { Observable, of, timer, merge, from } from 'rxjs';
|
||||
import { flatten, map as lodashMap, isArray, isString } from 'lodash';
|
||||
import { map, catchError, takeUntil, mapTo, share, finalize, tap } from 'rxjs/operators';
|
||||
// Utils & Services
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
// Types
|
||||
import {
|
||||
DataSourceApi,
|
||||
@ -136,7 +136,7 @@ export function runRequest(datasource: DataSourceApi, request: DataQueryRequest)
|
||||
|
||||
function cancelNetworkRequestsOnUnsubscribe(req: DataQueryRequest) {
|
||||
return () => {
|
||||
getBackendSrv().resolveCancelerIfExists(req.requestId);
|
||||
backendSrv.resolveCancelerIfExists(req.requestId);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import BasicSettings from './BasicSettings';
|
||||
import ButtonRow from './ButtonRow';
|
||||
// Services & Utils
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
// Actions & selectors
|
||||
import { getDataSource, getDataSourceMeta } from '../state/selectors';
|
||||
@ -145,7 +145,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
|
||||
this.setState({ isTesting: true, testingMessage: 'Testing...', testingStatus: 'info' });
|
||||
|
||||
getBackendSrv().withNoBackendCache(async () => {
|
||||
backendSrv.withNoBackendCache(async () => {
|
||||
try {
|
||||
const result = await dsApi.testDatasource();
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
import appEvents from 'app/core/app_events';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { ILocationService } from 'angular';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { ValidationSrv } from 'app/features/manage-dashboards';
|
||||
import { NavModelSrv } from 'app/core/nav_model_srv';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||
|
||||
export default class CreateFolderCtrl {
|
||||
title = '';
|
||||
@ -15,10 +17,10 @@ export default class CreateFolderCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private backendSrv: BackendSrv,
|
||||
private $location: ILocationService,
|
||||
private validationSrv: ValidationSrv,
|
||||
navModelSrv: NavModelSrv
|
||||
navModelSrv: NavModelSrv,
|
||||
private $scope: IScope
|
||||
) {
|
||||
this.navModel = navModelSrv.getNav('dashboards', 'manage-dashboards', 0);
|
||||
}
|
||||
@ -28,23 +30,27 @@ export default class CreateFolderCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.backendSrv.createFolder({ title: this.title }).then((result: any) => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
|
||||
this.$location.url(locationUtil.stripBaseFromUrl(result.url));
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
backendSrv.createFolder({ title: this.title }).then((result: any) => {
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
|
||||
this.$location.url(locationUtil.stripBaseFromUrl(result.url));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
titleChanged() {
|
||||
this.titleTouched = true;
|
||||
|
||||
this.validationSrv
|
||||
.validateNewFolderName(this.title)
|
||||
.then(() => {
|
||||
this.hasValidationError = false;
|
||||
})
|
||||
.catch(err => {
|
||||
this.hasValidationError = true;
|
||||
this.validationError = err.message;
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
this.validationSrv
|
||||
.validateNewFolderName(this.title)
|
||||
.then(() => {
|
||||
this.hasValidationError = false;
|
||||
})
|
||||
.catch(err => {
|
||||
this.hasValidationError = true;
|
||||
this.validationError = err.message;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
|
||||
import { FolderPageLoader } from './services/FolderPageLoader';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { ILocationService } from 'angular';
|
||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||
|
||||
export default class FolderDashboardsCtrl {
|
||||
navModel: any;
|
||||
@ -10,23 +12,25 @@ export default class FolderDashboardsCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private backendSrv: any,
|
||||
navModelSrv: NavModelSrv,
|
||||
private $routeParams: any,
|
||||
$location: ILocationService
|
||||
$location: ILocationService,
|
||||
private $scope: IScope
|
||||
) {
|
||||
if (this.$routeParams.uid) {
|
||||
this.uid = $routeParams.uid;
|
||||
|
||||
const loader = new FolderPageLoader(this.backendSrv);
|
||||
const loader = new FolderPageLoader();
|
||||
|
||||
loader.load(this, this.uid, 'manage-folder-dashboards').then((folder: any) => {
|
||||
const url = locationUtil.stripBaseFromUrl(folder.url);
|
||||
promiseToDigest(this.$scope)(
|
||||
loader.load(this, this.uid, 'manage-folder-dashboards').then((folder: any) => {
|
||||
const url = locationUtil.stripBaseFromUrl(folder.url);
|
||||
|
||||
if (url !== $location.path()) {
|
||||
$location.path(url).replace();
|
||||
}
|
||||
});
|
||||
if (url !== $location.path()) {
|
||||
$location.path(url).replace();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
export class FolderPageLoader {
|
||||
constructor(private backendSrv: BackendSrv) {}
|
||||
|
||||
load(ctrl: any, uid: any, activeChildId: any) {
|
||||
ctrl.navModel = {
|
||||
main: {
|
||||
@ -38,7 +36,7 @@ export class FolderPageLoader {
|
||||
},
|
||||
};
|
||||
|
||||
return this.backendSrv.getFolderByUid(uid).then((folder: any) => {
|
||||
return backendSrv.getFolderByUid(uid).then((folder: any) => {
|
||||
ctrl.folderId = folder.id;
|
||||
const folderTitle = folder.title;
|
||||
const folderUrl = folder.url;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { FolderState, ThunkResult } from 'app/types';
|
||||
import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel } from 'app/types/acl';
|
||||
|
||||
@ -11,7 +10,7 @@ import { loadFolder, loadFolderPermissions } from './reducers';
|
||||
|
||||
export function getFolderByUid(uid: string): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const folder = await getBackendSrv().getFolderByUid(uid);
|
||||
const folder = await backendSrv.getFolderByUid(uid);
|
||||
dispatch(loadFolder(folder));
|
||||
dispatch(updateNavIndex(buildNavModel(folder)));
|
||||
};
|
||||
@ -19,7 +18,7 @@ export function getFolderByUid(uid: string): ThunkResult<void> {
|
||||
|
||||
export function saveFolder(folder: FolderState): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const res = await getBackendSrv().put(`/api/folders/${folder.uid}`, {
|
||||
const res = await backendSrv.put(`/api/folders/${folder.uid}`, {
|
||||
title: folder.title,
|
||||
version: folder.version,
|
||||
});
|
||||
@ -33,14 +32,14 @@ export function saveFolder(folder: FolderState): ThunkResult<void> {
|
||||
|
||||
export function deleteFolder(uid: string): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
await getBackendSrv().deleteFolder(uid, true);
|
||||
await backendSrv.deleteFolder(uid, true);
|
||||
dispatch(updateLocation({ path: `dashboards` }));
|
||||
};
|
||||
}
|
||||
|
||||
export function getFolderPermissions(uid: string): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const permissions = await getBackendSrv().get(`/api/folders/${uid}/permissions`);
|
||||
const permissions = await backendSrv.get(`/api/folders/${uid}/permissions`);
|
||||
dispatch(loadFolderPermissions(permissions));
|
||||
};
|
||||
}
|
||||
@ -74,7 +73,7 @@ export function updateFolderPermission(itemToUpdate: DashboardAcl, level: Permis
|
||||
itemsToUpdate.push(updated);
|
||||
}
|
||||
|
||||
await getBackendSrv().post(`/api/folders/${folder.uid}/permissions`, { items: itemsToUpdate });
|
||||
await backendSrv.post(`/api/folders/${folder.uid}/permissions`, { items: itemsToUpdate });
|
||||
await dispatch(getFolderPermissions(folder.uid));
|
||||
};
|
||||
}
|
||||
@ -91,7 +90,7 @@ export function removeFolderPermission(itemToDelete: DashboardAcl): ThunkResult<
|
||||
itemsToUpdate.push(toUpdateItem(item));
|
||||
}
|
||||
|
||||
await getBackendSrv().post(`/api/folders/${folder.uid}/permissions`, { items: itemsToUpdate });
|
||||
await backendSrv.post(`/api/folders/${folder.uid}/permissions`, { items: itemsToUpdate });
|
||||
await dispatch(getFolderPermissions(folder.uid));
|
||||
};
|
||||
}
|
||||
@ -115,7 +114,7 @@ export function addFolderPermission(newItem: NewDashboardAclItem): ThunkResult<v
|
||||
permission: newItem.permission,
|
||||
});
|
||||
|
||||
await getBackendSrv().post(`/api/folders/${folder.uid}/permissions`, { items: itemsToUpdate });
|
||||
await backendSrv.post(`/api/folders/${folder.uid}/permissions`, { items: itemsToUpdate });
|
||||
await dispatch(getFolderPermissions(folder.uid));
|
||||
};
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { DashboardImportCtrl } from './DashboardImportCtrl';
|
||||
import config from 'app/core/config';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
describe('DashboardImportCtrl', () => {
|
||||
const ctx: any = {};
|
||||
jest.spyOn(backendSrv, 'getDashboardByUid').mockImplementation(() => Promise.resolve([]));
|
||||
jest.spyOn(backendSrv, 'search').mockImplementation(() => Promise.resolve([]));
|
||||
const getMock = jest.spyOn(backendSrv, 'get');
|
||||
|
||||
let navModelSrv: any;
|
||||
let backendSrv: any;
|
||||
let validationSrv: any;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -13,17 +16,13 @@ describe('DashboardImportCtrl', () => {
|
||||
getNav: () => {},
|
||||
};
|
||||
|
||||
backendSrv = {
|
||||
search: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||
getDashboardByUid: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
validationSrv = {
|
||||
validateNewDashboardName: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
};
|
||||
|
||||
ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {} as any, {} as any);
|
||||
ctx.ctrl = new DashboardImportCtrl(validationSrv, navModelSrv, {} as any, {} as any);
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when uploading json', () => {
|
||||
@ -61,16 +60,12 @@ describe('DashboardImportCtrl', () => {
|
||||
beforeEach(() => {
|
||||
ctx.ctrl.gnetUrl = 'http://grafana.com/dashboards/123';
|
||||
// setup api mock
|
||||
backendSrv.get = jest.fn(() => {
|
||||
return Promise.resolve({
|
||||
json: {},
|
||||
});
|
||||
});
|
||||
getMock.mockImplementation(() => Promise.resolve({ json: {} }));
|
||||
return ctx.ctrl.checkGnetDashboard();
|
||||
});
|
||||
|
||||
it('should call gnet api with correct dashboard id', () => {
|
||||
expect(backendSrv.get.mock.calls[0][0]).toBe('api/gnet/dashboards/123');
|
||||
expect(getMock.mock.calls[0][0]).toBe('api/gnet/dashboards/123');
|
||||
});
|
||||
});
|
||||
|
||||
@ -78,16 +73,12 @@ describe('DashboardImportCtrl', () => {
|
||||
beforeEach(() => {
|
||||
ctx.ctrl.gnetUrl = '2342';
|
||||
// setup api mock
|
||||
backendSrv.get = jest.fn(() => {
|
||||
return Promise.resolve({
|
||||
json: {},
|
||||
});
|
||||
});
|
||||
getMock.mockImplementation(() => Promise.resolve({ json: {} }));
|
||||
return ctx.ctrl.checkGnetDashboard();
|
||||
});
|
||||
|
||||
it('should call gnet api with correct dashboard id', () => {
|
||||
expect(backendSrv.get.mock.calls[0][0]).toBe('api/gnet/dashboards/2342');
|
||||
expect(getMock.mock.calls[0][0]).toBe('api/gnet/dashboards/2342');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
import config from 'app/core/config';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { BackendSrv } from '@grafana/runtime';
|
||||
import { ValidationSrv } from './services/ValidationSrv';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { ILocationService } from 'angular';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
export class DashboardImportCtrl {
|
||||
navModel: any;
|
||||
@ -32,7 +32,6 @@ export class DashboardImportCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private backendSrv: BackendSrv,
|
||||
private validationSrv: ValidationSrv,
|
||||
navModelSrv: NavModelSrv,
|
||||
private $location: ILocationService,
|
||||
@ -145,7 +144,7 @@ export class DashboardImportCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
this.backendSrv
|
||||
backendSrv
|
||||
// @ts-ignore
|
||||
.getDashboardByUid(this.dash.uid)
|
||||
.then((res: any) => {
|
||||
@ -185,7 +184,7 @@ export class DashboardImportCtrl {
|
||||
};
|
||||
});
|
||||
|
||||
return this.backendSrv
|
||||
return backendSrv
|
||||
.post('api/dashboards/import', {
|
||||
dashboard: this.dash,
|
||||
overwrite: true,
|
||||
@ -224,7 +223,7 @@ export class DashboardImportCtrl {
|
||||
this.gnetError = 'Could not find dashboard';
|
||||
}
|
||||
|
||||
return this.backendSrv
|
||||
return backendSrv
|
||||
.get('api/gnet/dashboards/' + dashboardId)
|
||||
.then(res => {
|
||||
this.gnetInfo = res;
|
||||
|
@ -1,9 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { ILocationService } from 'angular';
|
||||
import { BackendSrv } from '@grafana/runtime';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||
|
||||
export class SnapshotListCtrl {
|
||||
navModel: any;
|
||||
@ -12,27 +14,35 @@ export class SnapshotListCtrl {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $rootScope: GrafanaRootScope,
|
||||
private backendSrv: BackendSrv,
|
||||
navModelSrv: NavModelSrv,
|
||||
private $location: ILocationService
|
||||
private $location: ILocationService,
|
||||
private $scope: IScope
|
||||
) {
|
||||
this.navModel = navModelSrv.getNav('dashboards', 'snapshots', 0);
|
||||
this.backendSrv.get('/api/dashboard/snapshots').then((result: any) => {
|
||||
const baseUrl = this.$location.absUrl().replace($location.url(), '');
|
||||
this.snapshots = result.map((snapshot: any) => ({
|
||||
...snapshot,
|
||||
url: snapshot.externalUrl || `${baseUrl}/dashboard/snapshot/${snapshot.key}`,
|
||||
}));
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/dashboard/snapshots')
|
||||
.then((result: any) => {
|
||||
const baseUrl = this.$location.absUrl().replace($location.url(), '');
|
||||
this.snapshots = result.map((snapshot: any) => ({
|
||||
...snapshot,
|
||||
url: snapshot.externalUrl || `${baseUrl}/dashboard/snapshot/${snapshot.key}`,
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
removeSnapshotConfirmed(snapshot: any) {
|
||||
_.remove(this.snapshots, { key: snapshot.key });
|
||||
this.backendSrv.delete('/api/snapshots/' + snapshot.key).then(
|
||||
() => {},
|
||||
() => {
|
||||
this.snapshots.push(snapshot);
|
||||
}
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.delete('/api/snapshots/' + snapshot.key)
|
||||
.then(
|
||||
() => {},
|
||||
() => {
|
||||
this.snapshots.push(snapshot);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
|
||||
export class MoveToFolderCtrl {
|
||||
@ -10,15 +10,12 @@ export class MoveToFolderCtrl {
|
||||
afterSave: any;
|
||||
isValidFolderSelection = true;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv) {}
|
||||
|
||||
onFolderChange(folder: any) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
save() {
|
||||
return this.backendSrv.moveDashboards(this.dashboards, this.folder).then((result: any) => {
|
||||
return backendSrv.moveDashboards(this.dashboards, this.folder).then((result: any) => {
|
||||
if (result.successCount > 0) {
|
||||
const header = `Dashboard${result.successCount === 1 ? '' : 's'} Moved`;
|
||||
const msg = `${result.successCount} dashboard${result.successCount === 1 ? '' : 's'} moved to ${
|
||||
|
@ -1,5 +1,5 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
const hitTypes = {
|
||||
FOLDER: 'dash-folder',
|
||||
@ -9,9 +9,6 @@ const hitTypes = {
|
||||
export class ValidationSrv {
|
||||
rootName = 'general';
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv) {}
|
||||
|
||||
validateNewDashboardName(folderId: any, name: string) {
|
||||
return this.validate(folderId, name, 'A dashboard in this folder with the same name already exists');
|
||||
}
|
||||
@ -39,8 +36,8 @@ export class ValidationSrv {
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
promises.push(this.backendSrv.search({ type: hitTypes.FOLDER, folderIds: [folderId], query: name }));
|
||||
promises.push(this.backendSrv.search({ type: hitTypes.DASHBOARD, folderIds: [folderId], query: name }));
|
||||
promises.push(backendSrv.search({ type: hitTypes.FOLDER, folderIds: [folderId], query: name }));
|
||||
promises.push(backendSrv.search({ type: hitTypes.DASHBOARD, folderIds: [folderId], query: name }));
|
||||
|
||||
return Promise.all(promises).then(res => {
|
||||
let hits: any[] = [];
|
||||
|
@ -1,20 +1,24 @@
|
||||
import angular from 'angular';
|
||||
import config from 'app/core/config';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
|
||||
export class NewOrgCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: any, $http: any, backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
constructor($scope: any, $http: any, navModelSrv: NavModelSrv) {
|
||||
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
|
||||
$scope.newOrg = { name: '' };
|
||||
|
||||
$scope.createOrg = () => {
|
||||
backendSrv.post('/api/orgs/', $scope.newOrg).then((result: any) => {
|
||||
backendSrv.post('/api/user/using/' + result.orgId).then(() => {
|
||||
window.location.href = config.appSubUrl + '/org';
|
||||
getBackendSrv()
|
||||
.post('/api/orgs/', $scope.newOrg)
|
||||
.then((result: any) => {
|
||||
getBackendSrv()
|
||||
.post('/api/user/using/' + result.orgId)
|
||||
.then(() => {
|
||||
window.location.href = config.appSubUrl + '/org';
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import angular from 'angular';
|
||||
import config from 'app/core/config';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
|
||||
export class SelectOrgCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: any, backendSrv: BackendSrv, contextSrv: any) {
|
||||
constructor($scope: any, contextSrv: any) {
|
||||
contextSrv.sidemenu = false;
|
||||
|
||||
$scope.navModel = {
|
||||
@ -20,15 +21,21 @@ export class SelectOrgCtrl {
|
||||
};
|
||||
|
||||
$scope.getUserOrgs = () => {
|
||||
backendSrv.get('/api/user/orgs').then((orgs: any) => {
|
||||
$scope.orgs = orgs;
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/user/orgs')
|
||||
.then((orgs: any) => {
|
||||
$scope.orgs = orgs;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.setUsingOrg = (org: any) => {
|
||||
backendSrv.post('/api/user/using/' + org.orgId).then(() => {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
});
|
||||
getBackendSrv()
|
||||
.post('/api/user/using/' + org.orgId)
|
||||
.then(() => {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
});
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
@ -1,7 +1,8 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { ILocationService } from 'angular';
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
|
||||
export class UserInviteCtrl {
|
||||
navModel: any;
|
||||
@ -9,7 +10,7 @@ export class UserInviteCtrl {
|
||||
inviteForm: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv, private $location: ILocationService) {
|
||||
constructor(private $scope: IScope, navModelSrv: NavModelSrv, private $location: ILocationService) {
|
||||
this.navModel = navModelSrv.getNav('cfg', 'users', 0);
|
||||
|
||||
this.invite = {
|
||||
@ -25,9 +26,13 @@ export class UserInviteCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.backendSrv.post('/api/org/invites', this.invite).then(() => {
|
||||
this.$location.path('org/users/');
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/org/invites', this.invite)
|
||||
.then(() => {
|
||||
this.$location.path('org/users/');
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import './link_srv';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
function panelLinksEditor() {
|
||||
return {
|
||||
@ -17,7 +17,7 @@ function panelLinksEditor() {
|
||||
|
||||
export class PanelLinksEditorCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: any, backendSrv: BackendSrv) {
|
||||
constructor($scope: any) {
|
||||
$scope.panel.links = $scope.panel.links || [];
|
||||
|
||||
$scope.addLink = () => {
|
||||
|
@ -1,10 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from '../../core/core_module';
|
||||
import { ILocationService } from 'angular';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { ILocationService, IScope } from 'angular';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { NavModelSrv } from 'app/core/nav_model_srv';
|
||||
import { AppEventEmitter } from 'app/types';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||
|
||||
export interface PlaylistItem {
|
||||
value: any;
|
||||
@ -29,8 +30,7 @@ export class PlaylistEditCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $scope: AppEventEmitter,
|
||||
private backendSrv: BackendSrv,
|
||||
private $scope: IScope & AppEventEmitter,
|
||||
private $location: ILocationService,
|
||||
$route: any,
|
||||
navModelSrv: NavModelSrv
|
||||
@ -41,13 +41,21 @@ export class PlaylistEditCtrl {
|
||||
if ($route.current.params.id) {
|
||||
const playlistId = $route.current.params.id;
|
||||
|
||||
backendSrv.get('/api/playlists/' + playlistId).then((result: any) => {
|
||||
this.playlist = result;
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/playlists/' + playlistId)
|
||||
.then((result: any) => {
|
||||
this.playlist = result;
|
||||
})
|
||||
);
|
||||
|
||||
backendSrv.get('/api/playlists/' + playlistId + '/items').then((result: any) => {
|
||||
this.playlistItems = result;
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/playlists/' + playlistId + '/items')
|
||||
.then((result: any) => {
|
||||
this.playlistItems = result;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,8 +107,8 @@ export class PlaylistEditCtrl {
|
||||
playlist.items = playlistItems;
|
||||
|
||||
savePromise = playlist.id
|
||||
? this.backendSrv.put('/api/playlists/' + playlist.id, playlist)
|
||||
: this.backendSrv.post('/api/playlists', playlist);
|
||||
? promiseToDigest(this.$scope)(getBackendSrv().put('/api/playlists/' + playlist.id, playlist))
|
||||
: promiseToDigest(this.$scope)(getBackendSrv().post('/api/playlists', playlist));
|
||||
|
||||
savePromise.then(
|
||||
() => {
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { IScope, ITimeoutService } from 'angular';
|
||||
|
||||
import coreModule from '../../core/core_module';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||
|
||||
export class PlaylistSearchCtrl {
|
||||
query: any;
|
||||
@ -8,7 +11,7 @@ export class PlaylistSearchCtrl {
|
||||
searchStarted: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor($timeout: any, private backendSrv: BackendSrv) {
|
||||
constructor(private $scope: IScope, $timeout: ITimeoutService) {
|
||||
this.query = { query: '', tag: [], starred: false, limit: 20 };
|
||||
|
||||
$timeout(() => {
|
||||
@ -22,12 +25,14 @@ export class PlaylistSearchCtrl {
|
||||
this.tagsMode = false;
|
||||
const prom: any = {};
|
||||
|
||||
prom.promise = this.backendSrv.search(this.query).then(result => {
|
||||
return {
|
||||
dashboardResult: result,
|
||||
tagResult: [],
|
||||
};
|
||||
});
|
||||
prom.promise = promiseToDigest(this.$scope)(
|
||||
backendSrv.search(this.query).then(result => {
|
||||
return {
|
||||
dashboardResult: result,
|
||||
tagResult: [],
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.searchStarted(prom);
|
||||
}
|
||||
@ -52,12 +57,14 @@ export class PlaylistSearchCtrl {
|
||||
|
||||
getTags() {
|
||||
const prom: any = {};
|
||||
prom.promise = this.backendSrv.get('/api/dashboards/tags').then((result: any) => {
|
||||
return {
|
||||
dashboardResult: [],
|
||||
tagResult: result,
|
||||
} as any;
|
||||
});
|
||||
prom.promise = promiseToDigest(this.$scope)(
|
||||
backendSrv.get('/api/dashboards/tags').then((result: any) => {
|
||||
return {
|
||||
dashboardResult: [],
|
||||
tagResult: result,
|
||||
} as any;
|
||||
})
|
||||
);
|
||||
|
||||
this.searchStarted(prom);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import locationUtil from 'app/core/utils/location_util';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { store } from 'app/store/store';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
export const queryParamsToPreserve: { [key: string]: boolean } = {
|
||||
kiosk: true,
|
||||
@ -28,7 +29,7 @@ export class PlaylistSrv {
|
||||
isPlaying: boolean;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $location: any, private $timeout: any, private backendSrv: any) {}
|
||||
constructor(private $location: any, private $timeout: any) {}
|
||||
|
||||
next() {
|
||||
this.$timeout.cancel(this.cancelPromise);
|
||||
@ -89,13 +90,17 @@ export class PlaylistSrv {
|
||||
|
||||
appEvents.emit(CoreEvents.playlistStarted);
|
||||
|
||||
return this.backendSrv.get(`/api/playlists/${playlistId}`).then((playlist: any) => {
|
||||
return this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then((dashboards: any) => {
|
||||
this.dashboards = dashboards;
|
||||
this.interval = kbn.interval_to_ms(playlist.interval);
|
||||
this.next();
|
||||
return getBackendSrv()
|
||||
.get(`/api/playlists/${playlistId}`)
|
||||
.then((playlist: any) => {
|
||||
return getBackendSrv()
|
||||
.get(`/api/playlists/${playlistId}/dashboards`)
|
||||
.then((dashboards: any) => {
|
||||
this.dashboards = dashboards;
|
||||
this.interval = kbn.interval_to_ms(playlist.interval);
|
||||
this.next();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
@ -1,37 +1,47 @@
|
||||
import { IScope } from 'angular';
|
||||
import _ from 'lodash';
|
||||
import coreModule from '../../core/core_module';
|
||||
import { BackendSrv } from '@grafana/runtime';
|
||||
import { NavModelSrv } from 'app/core/nav_model_srv';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
import coreModule from '../../core/core_module';
|
||||
import { NavModelSrv } from 'app/core/nav_model_srv';
|
||||
import { AppEventEmitter, CoreEvents } from 'app/types';
|
||||
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||
|
||||
export class PlaylistsCtrl {
|
||||
playlists: any;
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope: any, private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
constructor(private $scope: IScope & AppEventEmitter, navModelSrv: NavModelSrv) {
|
||||
this.navModel = navModelSrv.getNav('dashboards', 'playlists', 0);
|
||||
|
||||
backendSrv.get('/api/playlists').then((result: any) => {
|
||||
this.playlists = result.map((item: any) => {
|
||||
item.startUrl = `playlists/play/${item.id}`;
|
||||
return item;
|
||||
});
|
||||
});
|
||||
promiseToDigest($scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/playlists')
|
||||
.then((result: any) => {
|
||||
this.playlists = result.map((item: any) => {
|
||||
item.startUrl = `playlists/play/${item.id}`;
|
||||
return item;
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
removePlaylistConfirmed(playlist: any) {
|
||||
_.remove(this.playlists, { id: playlist.id });
|
||||
|
||||
this.backendSrv.delete('/api/playlists/' + playlist.id).then(
|
||||
() => {
|
||||
this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist deleted']);
|
||||
},
|
||||
() => {
|
||||
this.$scope.appEvent(AppEvents.alertError, ['Unable to delete playlist']);
|
||||
this.playlists.push(playlist);
|
||||
}
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.delete('/api/playlists/' + playlist.id)
|
||||
.then(
|
||||
() => {
|
||||
this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist deleted']);
|
||||
},
|
||||
() => {
|
||||
this.$scope.appEvent(AppEvents.alertError, ['Unable to delete playlist']);
|
||||
this.playlists.push(playlist);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ describe('PlaylistEditCtrl', () => {
|
||||
},
|
||||
};
|
||||
|
||||
ctx = new PlaylistEditCtrl(null, null, null, { current: { params: {} } }, navModelSrv);
|
||||
ctx = new PlaylistEditCtrl(null, null, { current: { params: {} } }, navModelSrv);
|
||||
|
||||
ctx.dashboardresult = [
|
||||
{ id: 2, title: 'dashboard: 2' },
|
||||
|
@ -3,6 +3,18 @@ import configureMockStore from 'redux-mock-store';
|
||||
import { PlaylistSrv } from '../playlist_srv';
|
||||
import { setStore } from 'app/store/store';
|
||||
|
||||
const getMock = jest.fn();
|
||||
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
const original = jest.requireActual('@grafana/runtime');
|
||||
return {
|
||||
...original,
|
||||
getBackendSrv: () => ({
|
||||
get: getMock,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const mockStore = configureMockStore<any, any>();
|
||||
|
||||
setStore(
|
||||
@ -14,19 +26,6 @@ setStore(
|
||||
const dashboards = [{ url: 'dash1' }, { url: 'dash2' }];
|
||||
|
||||
const createPlaylistSrv = (): [PlaylistSrv, { url: jest.MockInstance<any, any> }] => {
|
||||
const mockBackendSrv = {
|
||||
get: jest.fn(url => {
|
||||
switch (url) {
|
||||
case '/api/playlists/1':
|
||||
return Promise.resolve({ interval: '1s' });
|
||||
case '/api/playlists/1/dashboards':
|
||||
return Promise.resolve(dashboards);
|
||||
default:
|
||||
throw new Error(`Unexpected url=${url}`);
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
const mockLocation = {
|
||||
url: jest.fn(),
|
||||
search: () => ({}),
|
||||
@ -36,7 +35,7 @@ const createPlaylistSrv = (): [PlaylistSrv, { url: jest.MockInstance<any, any> }
|
||||
const mockTimeout = jest.fn();
|
||||
(mockTimeout as any).cancel = jest.fn();
|
||||
|
||||
return [new PlaylistSrv(mockLocation, mockTimeout, mockBackendSrv), mockLocation];
|
||||
return [new PlaylistSrv(mockLocation, mockTimeout), mockLocation];
|
||||
};
|
||||
|
||||
const mockWindowLocation = (): [jest.MockInstance<any, any>, () => void] => {
|
||||
@ -67,6 +66,20 @@ describe('PlaylistSrv', () => {
|
||||
const initialUrl = 'http://localhost/playlist';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
getMock.mockImplementation(
|
||||
jest.fn(url => {
|
||||
switch (url) {
|
||||
case '/api/playlists/1':
|
||||
return Promise.resolve({ interval: '1s' });
|
||||
case '/api/playlists/1/dashboards':
|
||||
return Promise.resolve(dashboards);
|
||||
default:
|
||||
throw new Error(`Unexpected url=${url}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
[srv] = createPlaylistSrv();
|
||||
[hrefMock, unmockLocation] = mockWindowLocation();
|
||||
|
||||
|
@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
||||
import extend from 'lodash/extend';
|
||||
|
||||
import { PluginDashboard } from 'app/types';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import DashboardsTable from 'app/features/datasources/DashboardsTable';
|
||||
import { AppEvents, PluginMeta, DataSourceApi } from '@grafana/data';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { PluginMeta } from '@grafana/data';
|
||||
|
||||
type PluginCache = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Libraries
|
||||
import _ from 'lodash';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
// Services & Utils
|
||||
@ -17,7 +17,7 @@ import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
||||
|
||||
export class DatasourceSrv implements DataSourceService {
|
||||
datasources: Record<string, DataSourceApi>;
|
||||
datasources: Record<string, DataSourceApi> = {};
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
@ -38,7 +38,7 @@ export class DatasourceSrv implements DataSourceService {
|
||||
}
|
||||
|
||||
// Interpolation here is to support template variable in data source selection
|
||||
name = this.templateSrv.replace(name, scopedVars, (value: any[], variable: any) => {
|
||||
name = this.templateSrv.replace(name, scopedVars, (value: any[]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value[0];
|
||||
}
|
||||
@ -56,7 +56,7 @@ export class DatasourceSrv implements DataSourceService {
|
||||
return this.loadDatasource(name);
|
||||
}
|
||||
|
||||
loadDatasource(name: string): Promise<DataSourceApi<any, any>> {
|
||||
async loadDatasource(name: string): Promise<DataSourceApi<any, any>> {
|
||||
// Expression Datasource (not a real datasource)
|
||||
if (name === expressionDatasource.name) {
|
||||
this.datasources[name] = expressionDatasource as any;
|
||||
@ -68,32 +68,31 @@ export class DatasourceSrv implements DataSourceService {
|
||||
return Promise.reject({ message: `Datasource named ${name} was not found` });
|
||||
}
|
||||
|
||||
return importDataSourcePlugin(dsConfig.meta)
|
||||
.then(dsPlugin => {
|
||||
// check if its in cache now
|
||||
if (this.datasources[name]) {
|
||||
return this.datasources[name];
|
||||
}
|
||||
try {
|
||||
const dsPlugin = await importDataSourcePlugin(dsConfig.meta);
|
||||
// check if its in cache now
|
||||
if (this.datasources[name]) {
|
||||
return this.datasources[name];
|
||||
}
|
||||
|
||||
// If there is only one constructor argument it is instanceSettings
|
||||
const useAngular = dsPlugin.DataSourceClass.length !== 1;
|
||||
const instance: DataSourceApi = useAngular
|
||||
? this.$injector.instantiate(dsPlugin.DataSourceClass, {
|
||||
instanceSettings: dsConfig,
|
||||
})
|
||||
: new dsPlugin.DataSourceClass(dsConfig);
|
||||
// If there is only one constructor argument it is instanceSettings
|
||||
const useAngular = dsPlugin.DataSourceClass.length !== 1;
|
||||
const instance: DataSourceApi = useAngular
|
||||
? this.$injector.instantiate(dsPlugin.DataSourceClass, {
|
||||
instanceSettings: dsConfig,
|
||||
})
|
||||
: new dsPlugin.DataSourceClass(dsConfig);
|
||||
|
||||
instance.components = dsPlugin.components;
|
||||
instance.meta = dsConfig.meta;
|
||||
instance.components = dsPlugin.components;
|
||||
instance.meta = dsConfig.meta;
|
||||
|
||||
// store in instance cache
|
||||
this.datasources[name] = instance;
|
||||
return instance;
|
||||
})
|
||||
.catch(err => {
|
||||
this.$rootScope.appEvent(AppEvents.alertError, [dsConfig.name + ' plugin failed', err.toString()]);
|
||||
return undefined;
|
||||
});
|
||||
// store in instance cache
|
||||
this.datasources[name] = instance;
|
||||
return instance;
|
||||
} catch (err) {
|
||||
this.$rootScope.appEvent(AppEvents.alertError, [dsConfig.name + ' plugin failed', err.toString()]);
|
||||
return Promise.reject({ message: `Datasource named ${name} was not found` });
|
||||
}
|
||||
}
|
||||
|
||||
getAll() {
|
||||
@ -103,7 +102,7 @@ export class DatasourceSrv implements DataSourceService {
|
||||
|
||||
getExternal() {
|
||||
const datasources = this.getAll().filter(ds => !ds.meta.builtIn);
|
||||
return _.sortBy(datasources, ['name']);
|
||||
return sortBy(datasources, ['name']);
|
||||
}
|
||||
|
||||
getAnnotationSources() {
|
||||
@ -111,8 +110,8 @@ export class DatasourceSrv implements DataSourceService {
|
||||
|
||||
this.addDataSourceVariables(sources);
|
||||
|
||||
_.each(config.datasources, value => {
|
||||
if (value.meta && value.meta.annotations) {
|
||||
Object.values(config.datasources).forEach(value => {
|
||||
if (value.meta?.annotations) {
|
||||
sources.push(value);
|
||||
}
|
||||
});
|
||||
@ -123,8 +122,8 @@ export class DatasourceSrv implements DataSourceService {
|
||||
getMetricSources(options?: { skipVariables?: boolean }) {
|
||||
const metricSources: DataSourceSelectItem[] = [];
|
||||
|
||||
_.each(config.datasources, (value, key) => {
|
||||
if (value.meta && value.meta.metrics) {
|
||||
Object.entries(config.datasources).forEach(([key, value]) => {
|
||||
if (value.meta?.metrics) {
|
||||
let metricSource = { value: key, name: key, meta: value.meta, sort: key };
|
||||
|
||||
//Make sure grafana and mixed are sorted at the bottom
|
||||
@ -164,29 +163,22 @@ export class DatasourceSrv implements DataSourceService {
|
||||
|
||||
addDataSourceVariables(list: any[]) {
|
||||
// look for data source variables
|
||||
for (let i = 0; i < this.templateSrv.variables.length; i++) {
|
||||
const variable = this.templateSrv.variables[i];
|
||||
if (variable.type !== 'datasource') {
|
||||
continue;
|
||||
}
|
||||
this.templateSrv.variables
|
||||
.filter(variable => variable.type === 'datasource')
|
||||
.forEach(variable => {
|
||||
const first = variable.current.value === 'default' ? config.defaultDatasource : variable.current.value;
|
||||
const ds = config.datasources[first];
|
||||
|
||||
let first = variable.current.value;
|
||||
if (first === 'default') {
|
||||
first = config.defaultDatasource;
|
||||
}
|
||||
|
||||
const ds = config.datasources[first];
|
||||
|
||||
if (ds) {
|
||||
const key = `$${variable.name}`;
|
||||
list.push({
|
||||
name: key,
|
||||
value: key,
|
||||
meta: ds.meta,
|
||||
sort: key,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (ds) {
|
||||
const key = `$${variable.name}`;
|
||||
list.push({
|
||||
name: key,
|
||||
value: key,
|
||||
meta: ds.meta,
|
||||
sort: key,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,8 @@ import extend from 'lodash/extend';
|
||||
|
||||
import { Button } from '@grafana/ui';
|
||||
import { PluginMeta, AppPlugin, deprecationWarning } from '@grafana/data';
|
||||
import { AngularComponent, getAngularLoader, getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface Props {
|
||||
|
@ -1,62 +1,70 @@
|
||||
import { coreModule, NavModelSrv } from 'app/core/core';
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { UserSession } from 'app/types';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
import { IScope } from 'angular';
|
||||
|
||||
export class ProfileCtrl {
|
||||
sessions: object[] = [];
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
constructor(private $scope: IScope, navModelSrv: NavModelSrv) {
|
||||
this.getUserSessions();
|
||||
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
|
||||
}
|
||||
|
||||
getUserSessions() {
|
||||
this.backendSrv.get('/api/user/auth-tokens').then((sessions: UserSession[]) => {
|
||||
sessions.reverse();
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/user/auth-tokens')
|
||||
.then((sessions: UserSession[]) => {
|
||||
sessions.reverse();
|
||||
|
||||
const found = sessions.findIndex((session: UserSession) => {
|
||||
return session.isActive;
|
||||
});
|
||||
const found = sessions.findIndex((session: UserSession) => {
|
||||
return session.isActive;
|
||||
});
|
||||
|
||||
if (found) {
|
||||
const now = sessions[found];
|
||||
sessions.splice(found, found);
|
||||
sessions.unshift(now);
|
||||
}
|
||||
if (found) {
|
||||
const now = sessions[found];
|
||||
sessions.splice(found, found);
|
||||
sessions.unshift(now);
|
||||
}
|
||||
|
||||
this.sessions = sessions.map((session: UserSession) => {
|
||||
return {
|
||||
id: session.id,
|
||||
isActive: session.isActive,
|
||||
seenAt: dateTime(session.seenAt).fromNow(),
|
||||
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
||||
clientIp: session.clientIp,
|
||||
browser: session.browser,
|
||||
browserVersion: session.browserVersion,
|
||||
os: session.os,
|
||||
osVersion: session.osVersion,
|
||||
device: session.device,
|
||||
};
|
||||
});
|
||||
});
|
||||
this.sessions = sessions.map((session: UserSession) => {
|
||||
return {
|
||||
id: session.id,
|
||||
isActive: session.isActive,
|
||||
seenAt: dateTime(session.seenAt).fromNow(),
|
||||
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
||||
clientIp: session.clientIp,
|
||||
browser: session.browser,
|
||||
browserVersion: session.browserVersion,
|
||||
os: session.os,
|
||||
osVersion: session.osVersion,
|
||||
device: session.device,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
revokeUserSession(tokenId: number) {
|
||||
this.backendSrv
|
||||
.post('/api/user/revoke-auth-token', {
|
||||
authTokenId: tokenId,
|
||||
})
|
||||
.then(() => {
|
||||
this.sessions = this.sessions.filter((session: UserSession) => {
|
||||
if (session.id === tokenId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/user/revoke-auth-token', {
|
||||
authTokenId: tokenId,
|
||||
})
|
||||
.then(() => {
|
||||
this.sessions = this.sessions.filter((session: UserSession) => {
|
||||
if (session.id === tokenId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,7 @@ import { ConstantVariable } from './constant_variable';
|
||||
import { AdhocVariable } from './adhoc_variable';
|
||||
import { TextBoxVariable } from './TextBoxVariable';
|
||||
|
||||
coreModule.factory('templateSrv', () => {
|
||||
return templateSrv;
|
||||
});
|
||||
coreModule.factory('templateSrv', () => templateSrv);
|
||||
|
||||
export {
|
||||
VariableSrv,
|
||||
|
@ -31,7 +31,7 @@ const setup = () => {
|
||||
),
|
||||
]);
|
||||
|
||||
const datasource = new CloudWatchDatasource(instanceSettings, {} as any, templateSrv as any, {} as any);
|
||||
const datasource = new CloudWatchDatasource(instanceSettings, templateSrv as any, {} as any);
|
||||
datasource.metricFindQuery = async () => [{ value: 'test', label: 'test' }];
|
||||
|
||||
const props: Props = {
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
DataQueryRequest,
|
||||
DataSourceInstanceSettings,
|
||||
} from '@grafana/data';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
|
||||
@ -48,7 +48,6 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery,
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv,
|
||||
private timeSrv: TimeSrv
|
||||
) {
|
||||
@ -193,7 +192,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery,
|
||||
)}`;
|
||||
}
|
||||
|
||||
performTimeSeriesQuery(request: any, { from, to }: TimeRange) {
|
||||
performTimeSeriesQuery(request: any, { from, to }: TimeRange): Promise<any> {
|
||||
return this.awsRequest('/api/tsdb/query', request)
|
||||
.then((res: any) => {
|
||||
if (!res.results) {
|
||||
@ -530,9 +529,11 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery,
|
||||
data,
|
||||
};
|
||||
|
||||
return this.backendSrv.datasourceRequest(options).then((result: any) => {
|
||||
return result.data;
|
||||
});
|
||||
return getBackendSrv()
|
||||
.datasourceRequest(options)
|
||||
.then((result: any) => {
|
||||
return result.data;
|
||||
});
|
||||
}
|
||||
|
||||
getDefaultRegion() {
|
||||
|
@ -7,10 +7,17 @@ import { CustomVariable } from 'app/features/templating/all';
|
||||
import _ from 'lodash';
|
||||
import { CloudWatchQuery } from '../types';
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('CloudWatchDatasource', () => {
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
const instanceSettings = {
|
||||
jsonData: { defaultRegion: 'us-east-1' },
|
||||
name: 'TestDatasource',
|
||||
@ -29,14 +36,14 @@ describe('CloudWatchDatasource', () => {
|
||||
};
|
||||
},
|
||||
} as TimeSrv;
|
||||
const backendSrv = {} as BackendSrv;
|
||||
|
||||
const ctx = {
|
||||
backendSrv,
|
||||
templateSrv,
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.ds = new CloudWatchDatasource(instanceSettings, backendSrv, templateSrv, timeSrv);
|
||||
ctx.ds = new CloudWatchDatasource(instanceSettings, templateSrv, timeSrv);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('When performing CloudWatch query', () => {
|
||||
@ -86,7 +93,7 @@ describe('CloudWatchDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
||||
datasourceRequestMock.mockImplementation(params => {
|
||||
requestParams = params.data;
|
||||
return Promise.resolve({ data: response });
|
||||
});
|
||||
@ -174,7 +181,7 @@ describe('CloudWatchDatasource', () => {
|
||||
|
||||
describe('a correct cloudwatch url should be built for each time series in the response', () => {
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
||||
datasourceRequestMock.mockImplementation(params => {
|
||||
requestParams = params.data;
|
||||
return Promise.resolve({ data: response });
|
||||
});
|
||||
@ -291,7 +298,7 @@ describe('CloudWatchDatasource', () => {
|
||||
dispatch: jest.fn(),
|
||||
} as any);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(() => {
|
||||
datasourceRequestMock.mockImplementation(() => {
|
||||
return Promise.reject(backendErrorResponse);
|
||||
});
|
||||
});
|
||||
@ -310,10 +317,10 @@ describe('CloudWatchDatasource', () => {
|
||||
|
||||
describe('when regions query is used', () => {
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(() => {
|
||||
datasourceRequestMock.mockImplementation(() => {
|
||||
return Promise.resolve({});
|
||||
});
|
||||
ctx.ds = new CloudWatchDatasource(instanceSettings, backendSrv, templateSrv, timeSrv);
|
||||
ctx.ds = new CloudWatchDatasource(instanceSettings, templateSrv, timeSrv);
|
||||
ctx.ds.doMetricQueryRequest = jest.fn(() => []);
|
||||
});
|
||||
describe('and region param is left out', () => {
|
||||
@ -441,7 +448,7 @@ describe('CloudWatchDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
||||
datasourceRequestMock.mockImplementation(params => {
|
||||
return Promise.resolve({ data: response });
|
||||
});
|
||||
});
|
||||
@ -511,7 +518,7 @@ describe('CloudWatchDatasource', () => {
|
||||
),
|
||||
]);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
||||
datasourceRequestMock.mockImplementation(params => {
|
||||
requestParams = params.data;
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
@ -643,7 +650,7 @@ describe('CloudWatchDatasource', () => {
|
||||
scenario.setup = async (setupCallback: any) => {
|
||||
beforeEach(async () => {
|
||||
await setupCallback();
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(args => {
|
||||
datasourceRequestMock.mockImplementation(args => {
|
||||
scenario.request = args.data;
|
||||
return Promise.resolve({ data: scenario.requestResponse });
|
||||
});
|
||||
|
@ -3,16 +3,23 @@ import { dateMath, Field } from '@grafana/data';
|
||||
import _ from 'lodash';
|
||||
import { ElasticDatasource } from './datasource';
|
||||
import { toUtc, dateTime } from '@grafana/data';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { ElasticsearchOptions } from './types';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('ElasticDatasource', function(this: any) {
|
||||
const backendSrv: any = {
|
||||
datasourceRequest: jest.fn(),
|
||||
};
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const $rootScope = {
|
||||
$on: jest.fn(),
|
||||
@ -45,17 +52,11 @@ describe('ElasticDatasource', function(this: any) {
|
||||
|
||||
const ctx = {
|
||||
$rootScope,
|
||||
backendSrv,
|
||||
} as any;
|
||||
|
||||
function createDatasource(instanceSettings: DataSourceInstanceSettings<ElasticsearchOptions>) {
|
||||
instanceSettings.jsonData = instanceSettings.jsonData || ({} as ElasticsearchOptions);
|
||||
ctx.ds = new ElasticDatasource(
|
||||
instanceSettings,
|
||||
backendSrv as BackendSrv,
|
||||
templateSrv as TemplateSrv,
|
||||
timeSrv as TimeSrv
|
||||
);
|
||||
ctx.ds = new ElasticDatasource(instanceSettings, templateSrv as TemplateSrv, timeSrv as TimeSrv);
|
||||
}
|
||||
|
||||
describe('When testing datasource with index pattern', () => {
|
||||
@ -69,7 +70,7 @@ describe('ElasticDatasource', function(this: any) {
|
||||
|
||||
it('should translate index pattern to current day', () => {
|
||||
let requestOptions: any;
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
datasourceRequestMock.mockImplementation(options => {
|
||||
requestOptions = options;
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
@ -91,7 +92,7 @@ describe('ElasticDatasource', function(this: any) {
|
||||
jsonData: { interval: 'Daily', esVersion: 2 } as ElasticsearchOptions,
|
||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
datasourceRequestMock.mockImplementation(options => {
|
||||
requestOptions = options;
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
@ -165,7 +166,7 @@ describe('ElasticDatasource', function(this: any) {
|
||||
} as ElasticsearchOptions,
|
||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
datasourceRequestMock.mockImplementation(options => {
|
||||
return Promise.resolve(logsResponse);
|
||||
});
|
||||
|
||||
@ -225,7 +226,7 @@ describe('ElasticDatasource', function(this: any) {
|
||||
jsonData: { esVersion: 2 } as ElasticsearchOptions,
|
||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
datasourceRequestMock.mockImplementation(options => {
|
||||
requestOptions = options;
|
||||
return Promise.resolve({ data: { responses: [] } });
|
||||
});
|
||||
@ -266,7 +267,7 @@ describe('ElasticDatasource', function(this: any) {
|
||||
jsonData: { esVersion: 50 } as ElasticsearchOptions,
|
||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
datasourceRequestMock.mockImplementation(options => {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
metricbeat: {
|
||||
@ -362,7 +363,7 @@ describe('ElasticDatasource', function(this: any) {
|
||||
jsonData: { esVersion: 70 } as ElasticsearchOptions,
|
||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
datasourceRequestMock.mockImplementation(options => {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
'genuine.es7._mapping.response': {
|
||||
@ -515,7 +516,7 @@ describe('ElasticDatasource', function(this: any) {
|
||||
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
datasourceRequestMock.mockImplementation(options => {
|
||||
requestOptions = options;
|
||||
return Promise.resolve({ data: { responses: [] } });
|
||||
});
|
||||
@ -558,7 +559,7 @@ describe('ElasticDatasource', function(this: any) {
|
||||
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
datasourceRequestMock.mockImplementation(options => {
|
||||
requestOptions = options;
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
|
@ -12,7 +12,7 @@ import { IndexPattern } from './index_pattern';
|
||||
import { ElasticQueryBuilder } from './query_builder';
|
||||
import { toUtc } from '@grafana/data';
|
||||
import * as queryDef from './query_def';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery } from './types';
|
||||
@ -36,7 +36,6 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<ElasticsearchOptions>,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv,
|
||||
private timeSrv: TimeSrv
|
||||
) {
|
||||
@ -86,7 +85,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
||||
};
|
||||
}
|
||||
|
||||
return this.backendSrv.datasourceRequest(options);
|
||||
return getBackendSrv().datasourceRequest(options);
|
||||
}
|
||||
|
||||
private get(url: string) {
|
||||
@ -123,7 +122,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
||||
});
|
||||
}
|
||||
|
||||
annotationQuery(options: any) {
|
||||
annotationQuery(options: any): Promise<any> {
|
||||
const annotation = options.annotation;
|
||||
const timeField = annotation.timeField || '@timestamp';
|
||||
const timeEndField = annotation.timeEndField || null;
|
||||
@ -511,20 +510,20 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
||||
|
||||
metricFindQuery(query: any) {
|
||||
query = angular.fromJson(query);
|
||||
if (!query) {
|
||||
return Promise.resolve([]);
|
||||
if (query) {
|
||||
if (query.find === 'fields') {
|
||||
query.field = this.templateSrv.replace(query.field, {}, 'lucene');
|
||||
return this.getFields(query);
|
||||
}
|
||||
|
||||
if (query.find === 'terms') {
|
||||
query.field = this.templateSrv.replace(query.field, {}, 'lucene');
|
||||
query.query = this.templateSrv.replace(query.query || '*', {}, 'lucene');
|
||||
return this.getTerms(query);
|
||||
}
|
||||
}
|
||||
|
||||
if (query.find === 'fields') {
|
||||
query.field = this.templateSrv.replace(query.field, {}, 'lucene');
|
||||
return this.getFields(query);
|
||||
}
|
||||
|
||||
if (query.find === 'terms') {
|
||||
query.field = this.templateSrv.replace(query.field, {}, 'lucene');
|
||||
query.query = this.templateSrv.replace(query.query || '*', {}, 'lucene');
|
||||
return this.getTerms(query);
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
getTagKeys() {
|
||||
|
@ -1,20 +1,28 @@
|
||||
import Datasource from '../datasource';
|
||||
import { DataFrame, toUtc } from '@grafana/data';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('AppInsightsDatasource', () => {
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
const ctx: any = {
|
||||
backendSrv: {},
|
||||
templateSrv: new TemplateSrv(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
ctx.instanceSettings = {
|
||||
jsonData: { appInsightsAppId: '3ad4400f-ea7d-465d-a8fb-43fb20555d85' },
|
||||
url: 'http://appinsightsapi',
|
||||
};
|
||||
|
||||
ctx.ds = new Datasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
||||
ctx.ds = new Datasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
});
|
||||
|
||||
describe('When performing testDatasource', () => {
|
||||
@ -38,9 +46,9 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
datasourceRequestMock.mockImplementation(() => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return success status', () => {
|
||||
@ -63,9 +71,9 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
datasourceRequestMock.mockImplementation(() => {
|
||||
return Promise.reject(error);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error status and a detailed error message', () => {
|
||||
@ -91,9 +99,9 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
datasourceRequestMock.mockImplementation(() => {
|
||||
return Promise.reject(error);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error status and a detailed error message', () => {
|
||||
@ -151,7 +159,7 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
expect(options.url).toContain('/api/tsdb/query');
|
||||
expect(options.data.queries.length).toBe(1);
|
||||
expect(options.data.queries[0].refId).toBe('A');
|
||||
@ -160,7 +168,7 @@ describe('AppInsightsDatasource', () => {
|
||||
expect(options.data.queries[0].appInsights.valueColumn).toEqual('max');
|
||||
expect(options.data.queries[0].appInsights.segmentColumn).toBeUndefined();
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of datapoints', () => {
|
||||
@ -194,7 +202,7 @@ describe('AppInsightsDatasource', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
options.targets[0].appInsights.segmentColumn = 'partition';
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
expect(options.url).toContain('/api/tsdb/query');
|
||||
expect(options.data.queries.length).toBe(1);
|
||||
expect(options.data.queries[0].refId).toBe('A');
|
||||
@ -203,7 +211,7 @@ describe('AppInsightsDatasource', () => {
|
||||
expect(options.data.queries[0].appInsights.valueColumn).toEqual('max');
|
||||
expect(options.data.queries[0].appInsights.segmentColumn).toEqual('partition');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of datapoints', () => {
|
||||
@ -257,14 +265,14 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
expect(options.url).toContain('/api/tsdb/query');
|
||||
expect(options.data.queries.length).toBe(1);
|
||||
expect(options.data.queries[0].refId).toBe('A');
|
||||
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
||||
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a single datapoint', () => {
|
||||
@ -300,14 +308,14 @@ describe('AppInsightsDatasource', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
options.targets[0].appInsights.timeGrain = 'PT30M';
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
expect(options.url).toContain('/api/tsdb/query');
|
||||
expect(options.data.queries[0].refId).toBe('A');
|
||||
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
||||
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
||||
expect(options.data.queries[0].appInsights.timeGrain).toBe('PT30M');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of datapoints', () => {
|
||||
@ -355,13 +363,13 @@ describe('AppInsightsDatasource', () => {
|
||||
beforeEach(() => {
|
||||
options.targets[0].appInsights.dimension = 'client/city';
|
||||
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
expect(options.url).toContain('/api/tsdb/query');
|
||||
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
||||
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
||||
expect(options.data.queries[0].appInsights.dimension).toBe('client/city');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of datapoints', () => {
|
||||
@ -397,10 +405,10 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('/metrics/metadata');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of metric names', () => {
|
||||
@ -435,10 +443,10 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('/metrics/metadata');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of group bys', () => {
|
||||
@ -463,10 +471,10 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('/metrics/metadata');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of metric names', () => {
|
||||
@ -501,10 +509,10 @@ describe('AppInsightsDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('/metrics/metadata');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of group bys', () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { TimeSeries, toDataFrame } from '@grafana/data';
|
||||
import { DataQueryRequest, DataQueryResponseData, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import _ from 'lodash';
|
||||
|
||||
@ -21,11 +21,7 @@ export default class AppInsightsDatasource {
|
||||
logAnalyticsColumns: { [key: string]: LogAnalyticsColumn[] } = {};
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv
|
||||
) {
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>, private templateSrv: TemplateSrv) {
|
||||
this.id = instanceSettings.id;
|
||||
this.applicationId = instanceSettings.jsonData.appInsightsAppId;
|
||||
this.baseUrl = `/appinsights/${this.version}/apps/${this.applicationId}`;
|
||||
@ -119,7 +115,7 @@ export default class AppInsightsDatasource {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = await this.backendSrv.datasourceRequest({
|
||||
const { data } = await getBackendSrv().datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
data: {
|
||||
@ -228,8 +224,8 @@ export default class AppInsightsDatasource {
|
||||
});
|
||||
}
|
||||
|
||||
doRequest(url: any, maxRetries = 1) {
|
||||
return this.backendSrv
|
||||
doRequest(url: any, maxRetries = 1): Promise<any> {
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: this.url + url,
|
||||
method: 'GET',
|
||||
|
@ -4,10 +4,22 @@ import FakeSchemaData from './__mocks__/schema';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { KustoSchema } from '../types';
|
||||
import { toUtc } from '@grafana/data';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('AzureLogAnalyticsDatasource', () => {
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
datasourceRequestMock.mockImplementation(jest.fn());
|
||||
});
|
||||
|
||||
const ctx: any = {
|
||||
backendSrv: {},
|
||||
templateSrv: new TemplateSrv(),
|
||||
};
|
||||
|
||||
@ -17,7 +29,7 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
url: 'http://azureloganalyticsapi',
|
||||
};
|
||||
|
||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
});
|
||||
|
||||
describe('When the config option "Same as Azure Monitor" has been chosen', () => {
|
||||
@ -56,9 +68,9 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
ctx.instanceSettings.jsonData.tenantId = 'xxx';
|
||||
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
||||
ctx.instanceSettings.jsonData.azureLogAnalyticsSameAs = true;
|
||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
|
||||
workspacesUrl = options.url;
|
||||
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
||||
@ -66,7 +78,7 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
azureLogAnalyticsUrl = options.url;
|
||||
return Promise.resolve({ data: tableResponseWithOneColumn, status: 200 });
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
|
||||
});
|
||||
@ -94,9 +106,7 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
ctx.instanceSettings.jsonData.logAnalyticsSubscriptionId = 'xxx';
|
||||
ctx.instanceSettings.jsonData.logAnalyticsTenantId = 'xxx';
|
||||
ctx.instanceSettings.jsonData.logAnalyticsClientId = 'xxx';
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
return Promise.reject(error);
|
||||
};
|
||||
datasourceRequestMock.mockImplementation(() => Promise.reject(error));
|
||||
});
|
||||
|
||||
it('should return error status and a detailed error message', () => {
|
||||
@ -166,10 +176,10 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
describe('in time series format', () => {
|
||||
describe('and the data is valid (has time, metric and value columns)', () => {
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('query=AzureActivity');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of datapoints', () => {
|
||||
@ -205,10 +215,11 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('query=AzureActivity');
|
||||
return Promise.resolve({ data: invalidResponse, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an exception', () => {
|
||||
@ -222,10 +233,10 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
describe('in tableformat', () => {
|
||||
beforeEach(() => {
|
||||
options.targets[0].azureLogAnalytics.resultFormat = 'table';
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('query=AzureActivity');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of columns and rows', () => {
|
||||
@ -249,10 +260,10 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
|
||||
describe('When performing getSchema', () => {
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('metadata');
|
||||
return Promise.resolve({ data: FakeSchemaData.getlogAnalyticsFakeMetadata(), status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a schema with a table and rows', () => {
|
||||
@ -302,13 +313,13 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
let queryResults: any[];
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
|
||||
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
||||
} else {
|
||||
return Promise.resolve({ data: tableResponseWithOneColumn, status: 200 });
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
queryResults = await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
|
||||
});
|
||||
@ -364,13 +375,13 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
let annotationResults: any[];
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
|
||||
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
||||
} else {
|
||||
return Promise.resolve({ data: tableResponse, status: 200 });
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
annotationResults = await ctx.ds.annotationQuery({
|
||||
annotation: {
|
||||
|
@ -3,7 +3,7 @@ import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder
|
||||
import ResponseParser from './response_parser';
|
||||
import { AzureMonitorQuery, AzureDataSourceJsonData } from '../types';
|
||||
import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
export default class AzureLogAnalyticsDatasource {
|
||||
@ -18,7 +18,6 @@ export default class AzureLogAnalyticsDatasource {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv
|
||||
) {
|
||||
this.id = instanceSettings.id;
|
||||
@ -230,8 +229,8 @@ export default class AzureLogAnalyticsDatasource {
|
||||
});
|
||||
}
|
||||
|
||||
doRequest(url: string, maxRetries = 1) {
|
||||
return this.backendSrv
|
||||
doRequest(url: string, maxRetries = 1): Promise<any> {
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: this.url + url,
|
||||
method: 'GET',
|
||||
|
@ -2,21 +2,28 @@ import AzureMonitorDatasource from '../datasource';
|
||||
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { toUtc, DataFrame } from '@grafana/data';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('AzureMonitorDatasource', () => {
|
||||
const ctx: any = {
|
||||
backendSrv: {},
|
||||
templateSrv: new TemplateSrv(),
|
||||
};
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
ctx.instanceSettings = {
|
||||
url: 'http://azuremonitor.com',
|
||||
jsonData: { subscriptionId: '9935389e-9122-4ef9-95f9-1513dd24753f' },
|
||||
cloudName: 'azuremonitor',
|
||||
};
|
||||
|
||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
});
|
||||
|
||||
describe('When performing testDatasource', () => {
|
||||
@ -35,9 +42,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
beforeEach(() => {
|
||||
ctx.instanceSettings.jsonData.tenantId = 'xxx';
|
||||
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
return Promise.reject(error);
|
||||
};
|
||||
datasourceRequestMock.mockImplementation(() => Promise.reject(error));
|
||||
});
|
||||
|
||||
it('should return error status and a detailed error message', () => {
|
||||
@ -62,9 +67,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
beforeEach(() => {
|
||||
ctx.instanceSettings.jsonData.tenantId = 'xxx';
|
||||
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
datasourceRequestMock.mockImplementation(() => Promise.resolve({ data: response, status: 200 }));
|
||||
});
|
||||
|
||||
it('should return success status', () => {
|
||||
@ -124,10 +127,10 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('/api/tsdb/query');
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of datapoints', () => {
|
||||
@ -157,9 +160,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => Promise.resolve(response));
|
||||
});
|
||||
|
||||
it('should return a list of subscriptions', () => {
|
||||
@ -183,9 +184,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
|
||||
});
|
||||
|
||||
it('should return a list of resource groups', () => {
|
||||
@ -209,10 +208,10 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
expect(options.url).toContain('11112222-eeee-4949-9b2d-9106972f9123');
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of resource groups', () => {
|
||||
@ -243,12 +242,12 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of namespaces', () => {
|
||||
@ -277,12 +276,12 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
||||
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of namespaces', () => {
|
||||
@ -315,12 +314,12 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of resource names', () => {
|
||||
@ -353,12 +352,12 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
||||
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of resource names', () => {
|
||||
@ -397,7 +396,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||
expect(options.url).toBe(
|
||||
@ -406,7 +405,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
'metricdefinitions?api-version=2018-01-01&metricnamespace=default'
|
||||
);
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of metric names', () => {
|
||||
@ -446,7 +445,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
||||
expect(options.url).toBe(
|
||||
@ -455,7 +454,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
'metricdefinitions?api-version=2018-01-01&metricnamespace=default'
|
||||
);
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of metric names', () => {
|
||||
@ -497,7 +496,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||
expect(options.url).toBe(
|
||||
@ -505,7 +504,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
'/nodeapp/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview'
|
||||
);
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of metric names', () => {
|
||||
@ -545,7 +544,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
||||
expect(options.url).toBe(
|
||||
@ -553,7 +552,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
'/nodeapp/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview'
|
||||
);
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a list of metric namespaces', () => {
|
||||
@ -601,9 +600,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
|
||||
});
|
||||
|
||||
it('should return list of Resource Groups', () => {
|
||||
@ -625,9 +622,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = () => {
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
|
||||
});
|
||||
|
||||
it('should return list of Resource Groups', () => {
|
||||
@ -674,12 +669,12 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of Metric Definitions with no duplicates and no unsupported namespaces', () => {
|
||||
@ -725,12 +720,12 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of Resource Names', () => {
|
||||
@ -763,12 +758,12 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of Resource Names', () => {
|
||||
@ -828,7 +823,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
||||
const expected =
|
||||
@ -837,7 +832,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
||||
expect(options.url).toBe(expected);
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of Metric Definitions', () => {
|
||||
@ -900,7 +895,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
||||
const expected =
|
||||
@ -909,7 +904,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
||||
expect(options.url).toBe(expected);
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return Aggregation metadata for a Metric', () => {
|
||||
@ -974,7 +969,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||
const baseUrl =
|
||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
||||
const expected =
|
||||
@ -983,7 +978,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
||||
expect(options.url).toBe(expected);
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should return dimensions for a Metric that has dimensions', () => {
|
||||
|
@ -12,8 +12,8 @@ import {
|
||||
import { DataQueryRequest, DataQueryResponseData, DataSourceInstanceSettings } from '@grafana/data';
|
||||
|
||||
import { TimeSeries, toDataFrame } from '@grafana/data';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
export default class AzureMonitorDatasource {
|
||||
apiVersion = '2018-01-01';
|
||||
@ -31,7 +31,6 @@ export default class AzureMonitorDatasource {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv
|
||||
) {
|
||||
this.id = instanceSettings.id;
|
||||
@ -108,7 +107,7 @@ export default class AzureMonitorDatasource {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const { data } = await this.backendSrv.datasourceRequest({
|
||||
const { data } = await getBackendSrv().datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
data: {
|
||||
@ -434,8 +433,8 @@ export default class AzureMonitorDatasource {
|
||||
return field && field.length > 0;
|
||||
}
|
||||
|
||||
doRequest(url: string, maxRetries = 1) {
|
||||
return this.backendSrv
|
||||
doRequest(url: string, maxRetries = 1): Promise<any> {
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: this.url + url,
|
||||
method: 'GET',
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
import { MonitorConfig } from './MonitorConfig';
|
||||
import { AnalyticsConfig } from './AnalyticsConfig';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { InsightsConfig } from './InsightsConfig';
|
||||
import ResponseParser from '../azure_monitor/response_parser';
|
||||
import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData, AzureDataSourceSettings } from '../types';
|
||||
@ -38,7 +38,6 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
logAnalyticsSubscriptionId: '',
|
||||
};
|
||||
|
||||
this.backendSrv = getBackendSrv();
|
||||
this.templateSrv = new TemplateSrv();
|
||||
if (this.props.options.id) {
|
||||
updateDatasourcePluginOption(this.props, 'url', '/api/datasources/proxy/' + this.props.options.id);
|
||||
@ -46,7 +45,6 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
initPromise: CancelablePromise<any> = null;
|
||||
backendSrv: BackendSrv = null;
|
||||
templateSrv: TemplateSrv = null;
|
||||
|
||||
componentDidMount() {
|
||||
@ -157,7 +155,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onLoadSubscriptions = async (type?: string) => {
|
||||
await this.backendSrv
|
||||
await getBackendSrv()
|
||||
.put(`/api/datasources/${this.props.options.id}`, this.props.options)
|
||||
.then((result: AzureDataSourceSettings) => {
|
||||
updateDatasourcePluginOption(this.props, 'version', result.version);
|
||||
@ -173,7 +171,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
loadSubscriptions = async (route?: string) => {
|
||||
const url = `/${route || this.props.options.jsonData.cloudName}/subscriptions?api-version=2019-03-01`;
|
||||
|
||||
const result = await this.backendSrv.datasourceRequest({
|
||||
const result = await getBackendSrv().datasourceRequest({
|
||||
url: this.props.options.url + url,
|
||||
method: 'GET',
|
||||
});
|
||||
@ -198,7 +196,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
azureMonitorUrl +
|
||||
`/${subscriptionId}/providers/Microsoft.OperationalInsights/workspaces?api-version=2017-04-26-preview`;
|
||||
|
||||
const result = await this.backendSrv.datasourceRequest({
|
||||
const result = await getBackendSrv().datasourceRequest({
|
||||
url: this.props.options.url + workspaceListUrl,
|
||||
method: 'GET',
|
||||
});
|
||||
|
@ -4,7 +4,6 @@ import AppInsightsDatasource from './app_insights/app_insights_datasource';
|
||||
import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
|
||||
import { AzureMonitorQuery, AzureDataSourceJsonData } from './types';
|
||||
import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
|
||||
@ -13,20 +12,12 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
||||
azureLogAnalyticsDatasource: AzureLogAnalyticsDatasource;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv
|
||||
) {
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>, private templateSrv: TemplateSrv) {
|
||||
super(instanceSettings);
|
||||
this.azureMonitorDatasource = new AzureMonitorDatasource(instanceSettings, this.backendSrv, this.templateSrv);
|
||||
this.appInsightsDatasource = new AppInsightsDatasource(instanceSettings, this.backendSrv, this.templateSrv);
|
||||
this.azureMonitorDatasource = new AzureMonitorDatasource(instanceSettings, this.templateSrv);
|
||||
this.appInsightsDatasource = new AppInsightsDatasource(instanceSettings, this.templateSrv);
|
||||
|
||||
this.azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(
|
||||
instanceSettings,
|
||||
this.backendSrv,
|
||||
this.templateSrv
|
||||
);
|
||||
this.azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(instanceSettings, this.templateSrv);
|
||||
}
|
||||
|
||||
async query(options: DataQueryRequest<AzureMonitorQuery>) {
|
||||
|
@ -1,13 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
class GrafanaDatasource {
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: BackendSrv, private templateSrv: TemplateSrv) {}
|
||||
constructor(private templateSrv: TemplateSrv) {}
|
||||
|
||||
query(options: any) {
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.get('/api/tsdb/testdata/random-walk', {
|
||||
from: options.range.from.valueOf(),
|
||||
to: options.range.to.valueOf(),
|
||||
@ -76,7 +76,7 @@ class GrafanaDatasource {
|
||||
params.tags = tags;
|
||||
}
|
||||
|
||||
return this.backendSrv.get('/api/annotations', params);
|
||||
return getBackendSrv().get('/api/annotations', params);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,25 +1,37 @@
|
||||
import { GrafanaDatasource } from '../datasource';
|
||||
// @ts-ignore
|
||||
import q from 'q';
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
import { GrafanaDatasource } from '../datasource';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('grafana data source', () => {
|
||||
const getMock = jest.spyOn(backendSrv, 'get');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when executing an annotations query', () => {
|
||||
let calledBackendSrvParams: any;
|
||||
const backendSrvStub = {
|
||||
get: (url: string, options: any) => {
|
||||
let templateSrvStub: any;
|
||||
let ds: GrafanaDatasource;
|
||||
beforeEach(() => {
|
||||
getMock.mockImplementation((url: string, options: any) => {
|
||||
calledBackendSrvParams = options;
|
||||
return q.resolve([]);
|
||||
},
|
||||
};
|
||||
return Promise.resolve([]);
|
||||
});
|
||||
|
||||
const templateSrvStub = {
|
||||
replace: (val: string) => {
|
||||
return val.replace('$var2', 'replaced__delimiter__replaced2').replace('$var', 'replaced');
|
||||
},
|
||||
};
|
||||
templateSrvStub = {
|
||||
replace: (val: string) => {
|
||||
return val.replace('$var2', 'replaced__delimiter__replaced2').replace('$var', 'replaced');
|
||||
},
|
||||
};
|
||||
|
||||
const ds = new GrafanaDatasource(backendSrvStub as any, templateSrvStub as any);
|
||||
ds = new GrafanaDatasource(templateSrvStub as any);
|
||||
});
|
||||
|
||||
describe('with tags that have template variables', () => {
|
||||
const options = setupAnnotationQueryOptions({ tags: ['tag1:$var'] });
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
|
||||
import gfunc from './gfunc';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
//Types
|
||||
import { GraphiteOptions, GraphiteQuery, GraphiteType } from './types';
|
||||
@ -30,7 +30,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
_seriesRefLetters: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(instanceSettings: any, private backendSrv: BackendSrv, private templateSrv: TemplateSrv) {
|
||||
constructor(instanceSettings: any, private templateSrv: TemplateSrv) {
|
||||
super(instanceSettings);
|
||||
this.basicAuth = instanceSettings.basicAuth;
|
||||
this.url = instanceSettings.url;
|
||||
@ -571,7 +571,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
options.url = this.url + options.url;
|
||||
options.inspect = { type: 'graphite' };
|
||||
|
||||
return this.backendSrv.datasourceRequest(options);
|
||||
return getBackendSrv().datasourceRequest(options);
|
||||
}
|
||||
|
||||
buildGraphiteParams(options: any, scopedVars: ScopedVars): string[] {
|
||||
|
@ -3,19 +3,26 @@ import _ from 'lodash';
|
||||
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('graphiteDatasource', () => {
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
const ctx: any = {
|
||||
backendSrv: {},
|
||||
// @ts-ignore
|
||||
templateSrv: new TemplateSrv(),
|
||||
instanceSettings: { url: 'url', name: 'graphiteProd', jsonData: {} },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
ctx.instanceSettings.url = '/api/datasources/proxy/1';
|
||||
// @ts-ignore
|
||||
ctx.ds = new GraphiteDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
||||
ctx.ds = new GraphiteDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
});
|
||||
|
||||
describe('When querying graphite with one target using query editor target spec', () => {
|
||||
@ -31,7 +38,7 @@ describe('graphiteDatasource', () => {
|
||||
let requestOptions: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
requestOptions = options;
|
||||
return Promise.resolve({
|
||||
data: [
|
||||
@ -44,7 +51,7 @@ describe('graphiteDatasource', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
await ctx.ds.query(query).then((data: any) => {
|
||||
results = data;
|
||||
@ -115,10 +122,9 @@ describe('graphiteDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
|
||||
});
|
||||
await ctx.ds.annotationQuery(options).then((data: any) => {
|
||||
results = data;
|
||||
});
|
||||
@ -145,9 +151,9 @@ describe('graphiteDatasource', () => {
|
||||
],
|
||||
};
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
});
|
||||
|
||||
ctx.ds.annotationQuery(options).then((data: any) => {
|
||||
results = data;
|
||||
@ -263,12 +269,12 @@ describe('graphiteDatasource', () => {
|
||||
let requestOptions: any;
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
requestOptions = options;
|
||||
return Promise.resolve({
|
||||
data: ['backend_01', 'backend_02'],
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate tags query', () => {
|
||||
@ -390,7 +396,6 @@ describe('graphiteDatasource', () => {
|
||||
function accessScenario(name: string, url: string, fn: any) {
|
||||
describe('access scenario ' + name, () => {
|
||||
const ctx: any = {
|
||||
backendSrv: {},
|
||||
// @ts-ignore
|
||||
templateSrv: new TemplateSrv(),
|
||||
instanceSettings: { url: 'url', name: 'graphiteProd', jsonData: {} },
|
||||
@ -405,8 +410,7 @@ function accessScenario(name: string, url: string, fn: any) {
|
||||
|
||||
it('tracing headers should be added', () => {
|
||||
ctx.instanceSettings.url = url;
|
||||
// @ts-ignore
|
||||
const ds = new GraphiteDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
||||
const ds = new GraphiteDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
ds.addTracingHeaders(httpOptions, options);
|
||||
fn(httpOptions);
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import InfluxQueryModel from './influx_query_model';
|
||||
import ResponseParser from './response_parser';
|
||||
import { InfluxQueryBuilder } from './query_builder';
|
||||
import { InfluxQuery, InfluxOptions } from './types';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxOptions> {
|
||||
@ -23,11 +23,7 @@ export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxO
|
||||
httpMode: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<InfluxOptions>,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv
|
||||
) {
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>, private templateSrv: TemplateSrv) {
|
||||
super(instanceSettings);
|
||||
this.type = 'influxdb';
|
||||
this.urls = _.map(instanceSettings.url.split(','), url => {
|
||||
@ -208,7 +204,7 @@ export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxO
|
||||
metricFindQuery(query: string, options?: any) {
|
||||
const interpolated = this.templateSrv.replace(query, null, 'regex');
|
||||
|
||||
return this._seriesQuery(interpolated, options).then(_.curry(this.responseParser.parse)(query));
|
||||
return this._seriesQuery(interpolated, options).then(() => this.responseParser.parse(query));
|
||||
}
|
||||
|
||||
getTagKeys(options: any = {}) {
|
||||
@ -320,28 +316,30 @@ export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxO
|
||||
req.headers['Content-type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
return this.backendSrv.datasourceRequest(req).then(
|
||||
(result: any) => {
|
||||
return result.data;
|
||||
},
|
||||
(err: any) => {
|
||||
if (err.status !== 0 || err.status >= 300) {
|
||||
if (err.data && err.data.error) {
|
||||
throw {
|
||||
message: 'InfluxDB Error: ' + err.data.error,
|
||||
data: err.data,
|
||||
config: err.config,
|
||||
};
|
||||
} else {
|
||||
throw {
|
||||
message: 'Network Error: ' + err.statusText + '(' + err.status + ')',
|
||||
data: err.data,
|
||||
config: err.config,
|
||||
};
|
||||
return getBackendSrv()
|
||||
.datasourceRequest(req)
|
||||
.then(
|
||||
(result: any) => {
|
||||
return result.data;
|
||||
},
|
||||
(err: any) => {
|
||||
if (err.status !== 0 || err.status >= 300) {
|
||||
if (err.data && err.data.error) {
|
||||
throw {
|
||||
message: 'InfluxDB Error: ' + err.data.error,
|
||||
data: err.data,
|
||||
config: err.config,
|
||||
};
|
||||
} else {
|
||||
throw {
|
||||
message: 'Network Error: ' + err.statusText + '(' + err.status + ')',
|
||||
data: err.data,
|
||||
config: err.config,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
getTimeFilter(options: any) {
|
||||
|
@ -1,18 +1,26 @@
|
||||
import InfluxDatasource from '../datasource';
|
||||
|
||||
import { TemplateSrvStub } from 'test/specs/helpers';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('InfluxDataSource', () => {
|
||||
const ctx: any = {
|
||||
backendSrv: {},
|
||||
//@ts-ignore
|
||||
templateSrv: new TemplateSrvStub(),
|
||||
instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'GET' } },
|
||||
};
|
||||
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
ctx.instanceSettings.url = '/api/datasources/proxy/1';
|
||||
ctx.ds = new InfluxDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
||||
ctx.ds = new InfluxDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
});
|
||||
|
||||
describe('When issuing metricFindQuery', () => {
|
||||
@ -26,7 +34,7 @@ describe('InfluxDataSource', () => {
|
||||
let requestQuery: any, requestMethod: any, requestData: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx.backendSrv.datasourceRequest = (req: any) => {
|
||||
datasourceRequestMock.mockImplementation((req: any) => {
|
||||
requestMethod = req.method;
|
||||
requestQuery = req.params.q;
|
||||
requestData = req.data;
|
||||
@ -43,7 +51,7 @@ describe('InfluxDataSource', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
await ctx.ds.metricFindQuery(query, queryOptions).then(() => {});
|
||||
});
|
||||
@ -60,60 +68,59 @@ describe('InfluxDataSource', () => {
|
||||
expect(requestData).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('InfluxDataSource in POST query mode', () => {
|
||||
const ctx: any = {
|
||||
backendSrv: {},
|
||||
//@ts-ignore
|
||||
templateSrv: new TemplateSrvStub(),
|
||||
instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'POST' } },
|
||||
};
|
||||
describe('InfluxDataSource in POST query mode', () => {
|
||||
const ctx: any = {
|
||||
//@ts-ignore
|
||||
templateSrv: new TemplateSrvStub(),
|
||||
instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'POST' } },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.instanceSettings.url = '/api/datasources/proxy/1';
|
||||
ctx.ds = new InfluxDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
||||
});
|
||||
beforeEach(() => {
|
||||
ctx.instanceSettings.url = '/api/datasources/proxy/1';
|
||||
ctx.ds = new InfluxDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
});
|
||||
|
||||
describe('When issuing metricFindQuery', () => {
|
||||
const query = 'SELECT max(value) FROM measurement';
|
||||
const queryOptions: any = {};
|
||||
let requestMethod: any, requestQueryParameter: any, queryEncoded: any, requestQuery: any;
|
||||
describe('When issuing metricFindQuery', () => {
|
||||
const query = 'SELECT max(value) FROM measurement';
|
||||
const queryOptions: any = {};
|
||||
let requestMethod: any, requestQueryParameter: any, queryEncoded: any, requestQuery: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx.backendSrv.datasourceRequest = (req: any) => {
|
||||
requestMethod = req.method;
|
||||
requestQueryParameter = req.params;
|
||||
requestQuery = req.data;
|
||||
return Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
series: [
|
||||
{
|
||||
name: 'measurement',
|
||||
columns: ['max'],
|
||||
values: [[1]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
beforeEach(async () => {
|
||||
datasourceRequestMock.mockImplementation((req: any) => {
|
||||
requestMethod = req.method;
|
||||
requestQueryParameter = req.params;
|
||||
requestQuery = req.data;
|
||||
return Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
series: [
|
||||
{
|
||||
name: 'measurement',
|
||||
columns: ['max'],
|
||||
values: [[1]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
queryEncoded = await ctx.ds.serializeParams({ q: query });
|
||||
await ctx.ds.metricFindQuery(query, queryOptions).then(() => {});
|
||||
});
|
||||
queryEncoded = await ctx.ds.serializeParams({ q: query });
|
||||
await ctx.ds.metricFindQuery(query, queryOptions).then(() => {});
|
||||
});
|
||||
|
||||
it('should have the query form urlencoded', () => {
|
||||
expect(requestQuery).toBe(queryEncoded);
|
||||
});
|
||||
it('should have the query form urlencoded', () => {
|
||||
expect(requestQuery).toBe(queryEncoded);
|
||||
});
|
||||
|
||||
it('should use the HTTP POST method', () => {
|
||||
expect(requestMethod).toBe('POST');
|
||||
});
|
||||
it('should use the HTTP POST method', () => {
|
||||
expect(requestMethod).toBe('POST');
|
||||
});
|
||||
|
||||
it('should not have q as a query parameter', () => {
|
||||
expect(requestQueryParameter).not.toHaveProperty('q');
|
||||
it('should not have q as a query parameter', () => {
|
||||
expect(requestQueryParameter).not.toHaveProperty('q');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2,13 +2,20 @@ import LokiDatasource, { RangeQueryOptions } from './datasource';
|
||||
import { LokiQuery, LokiResultType, LokiResponse, LokiLegacyStreamResponse } from './types';
|
||||
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
||||
import { AnnotationQueryRequest, DataSourceApi, DataFrame, dateTime, TimeRange } from '@grafana/data';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { CustomVariable } from 'app/features/templating/custom_variable';
|
||||
import { makeMockLokiDatasource } from './mocks';
|
||||
import { ExploreMode } from 'app/types';
|
||||
import { of } from 'rxjs';
|
||||
import omit from 'lodash/omit';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
describe('LokiDatasource', () => {
|
||||
const instanceSettings: any = {
|
||||
@ -42,8 +49,10 @@ describe('LokiDatasource', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const backendSrvMock = { datasourceRequest: jest.fn() };
|
||||
const backendSrv = (backendSrvMock as unknown) as BackendSrv;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
datasourceRequestMock.mockImplementation(() => Promise.resolve());
|
||||
});
|
||||
|
||||
const templateSrvMock = ({
|
||||
getAdhocFilters: (): any[] => [],
|
||||
@ -56,7 +65,7 @@ describe('LokiDatasource', () => {
|
||||
beforeEach(() => {
|
||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
||||
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||
adjustIntervalSpy = jest.spyOn(ds, 'adjustInterval');
|
||||
});
|
||||
|
||||
@ -95,8 +104,8 @@ describe('LokiDatasource', () => {
|
||||
beforeEach(() => {
|
||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
||||
backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(legacyTestResp));
|
||||
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||
datasourceRequestMock.mockImplementation(() => Promise.resolve(legacyTestResp));
|
||||
});
|
||||
|
||||
test('should try latest endpoint but fall back to legacy endpoint if it cannot be reached', async () => {
|
||||
@ -112,14 +121,18 @@ describe('LokiDatasource', () => {
|
||||
});
|
||||
|
||||
describe('when querying', () => {
|
||||
const testLimit = makeLimitTest(instanceSettings, backendSrvMock, backendSrv, templateSrvMock, legacyTestResp);
|
||||
let ds: LokiDatasource;
|
||||
let testLimit: any;
|
||||
|
||||
beforeAll(() => {
|
||||
testLimit = makeLimitTest(instanceSettings, datasourceRequestMock, templateSrvMock, legacyTestResp);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
||||
backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
|
||||
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||
datasourceRequestMock.mockImplementation(() => Promise.resolve(testResp));
|
||||
});
|
||||
|
||||
test('should run instant query and range query when in metrics mode', async () => {
|
||||
@ -183,11 +196,13 @@ describe('LokiDatasource', () => {
|
||||
test('should return series data', async () => {
|
||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||
const ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
||||
backendSrvMock.datasourceRequest = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(Promise.resolve(legacyTestResp))
|
||||
.mockReturnValueOnce(Promise.resolve(omit(legacyTestResp, 'status')));
|
||||
const ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||
datasourceRequestMock.mockImplementation(
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(Promise.resolve(legacyTestResp))
|
||||
.mockReturnValueOnce(Promise.resolve(omit(legacyTestResp, 'status')))
|
||||
);
|
||||
|
||||
const options = getQueryOptions<LokiQuery>({
|
||||
targets: [{ expr: '{job="grafana"} |= "foo"', refId: 'B' }],
|
||||
@ -209,7 +224,7 @@ describe('LokiDatasource', () => {
|
||||
beforeEach(() => {
|
||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
||||
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||
variable = new CustomVariable({}, {} as any);
|
||||
});
|
||||
|
||||
@ -256,17 +271,15 @@ describe('LokiDatasource', () => {
|
||||
|
||||
describe('and call succeeds', () => {
|
||||
beforeEach(async () => {
|
||||
const backendSrv = ({
|
||||
async datasourceRequest() {
|
||||
return Promise.resolve({
|
||||
status: 200,
|
||||
data: {
|
||||
data: ['avalue'],
|
||||
},
|
||||
});
|
||||
},
|
||||
} as unknown) as BackendSrv;
|
||||
ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
|
||||
datasourceRequestMock.mockImplementation(async () => {
|
||||
return Promise.resolve({
|
||||
status: 200,
|
||||
data: {
|
||||
values: ['avalue'],
|
||||
},
|
||||
});
|
||||
});
|
||||
ds = new LokiDatasource(instanceSettings, {} as TemplateSrv);
|
||||
result = await ds.testDatasource();
|
||||
});
|
||||
|
||||
@ -278,7 +291,7 @@ describe('LokiDatasource', () => {
|
||||
describe('and call fails with 401 error', () => {
|
||||
let ds: LokiDatasource;
|
||||
beforeEach(() => {
|
||||
backendSrvMock.datasourceRequest = jest.fn(() =>
|
||||
datasourceRequestMock.mockImplementation(() =>
|
||||
Promise.reject({
|
||||
statusText: 'Unauthorized',
|
||||
status: 401,
|
||||
@ -290,7 +303,7 @@ describe('LokiDatasource', () => {
|
||||
|
||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
||||
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||
});
|
||||
|
||||
it('should return error status and a detailed error message', async () => {
|
||||
@ -302,16 +315,14 @@ describe('LokiDatasource', () => {
|
||||
|
||||
describe('and call fails with 404 error', () => {
|
||||
beforeEach(async () => {
|
||||
const backendSrv = ({
|
||||
async datasourceRequest() {
|
||||
return Promise.reject({
|
||||
statusText: 'Not found',
|
||||
status: 404,
|
||||
data: '404 page not found',
|
||||
});
|
||||
},
|
||||
} as unknown) as BackendSrv;
|
||||
ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
|
||||
datasourceRequestMock.mockImplementation(() =>
|
||||
Promise.reject({
|
||||
statusText: 'Not found',
|
||||
status: 404,
|
||||
data: '404 page not found',
|
||||
})
|
||||
);
|
||||
ds = new LokiDatasource(instanceSettings, {} as TemplateSrv);
|
||||
result = await ds.testDatasource();
|
||||
});
|
||||
|
||||
@ -323,16 +334,14 @@ describe('LokiDatasource', () => {
|
||||
|
||||
describe('and call fails with 502 error', () => {
|
||||
beforeEach(async () => {
|
||||
const backendSrv = ({
|
||||
async datasourceRequest() {
|
||||
return Promise.reject({
|
||||
statusText: 'Bad Gateway',
|
||||
status: 502,
|
||||
data: '',
|
||||
});
|
||||
},
|
||||
} as unknown) as BackendSrv;
|
||||
ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
|
||||
datasourceRequestMock.mockImplementation(() =>
|
||||
Promise.reject({
|
||||
statusText: 'Bad Gateway',
|
||||
status: 502,
|
||||
data: '',
|
||||
})
|
||||
);
|
||||
ds = new LokiDatasource(instanceSettings, {} as TemplateSrv);
|
||||
result = await ds.testDatasource();
|
||||
});
|
||||
|
||||
@ -344,7 +353,7 @@ describe('LokiDatasource', () => {
|
||||
});
|
||||
|
||||
describe('when creating a range query', () => {
|
||||
const ds = new LokiDatasource(instanceSettings, backendSrv, templateSrvMock);
|
||||
const ds = new LokiDatasource(instanceSettings, templateSrvMock);
|
||||
const query: LokiQuery = { expr: 'foo', refId: 'bar' };
|
||||
|
||||
// Loki v1 API has an issue with float step parameters, can be removed when API is fixed
|
||||
@ -362,31 +371,33 @@ describe('LokiDatasource', () => {
|
||||
|
||||
describe('annotationQuery', () => {
|
||||
it('should transform the loki data to annotation response', async () => {
|
||||
const ds = new LokiDatasource(instanceSettings, backendSrv, templateSrvMock);
|
||||
backendSrvMock.datasourceRequest = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
data: [],
|
||||
status: 404,
|
||||
})
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
data: {
|
||||
streams: [
|
||||
{
|
||||
entries: [{ ts: '2019-02-01T10:27:37.498180581Z', line: 'hello' }],
|
||||
labels: '{label="value"}',
|
||||
},
|
||||
{
|
||||
entries: [{ ts: '2019-02-01T12:27:37.498180581Z', line: 'hello 2' }],
|
||||
labels: '{label2="value2"}',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
const ds = new LokiDatasource(instanceSettings, templateSrvMock);
|
||||
datasourceRequestMock.mockImplementation(
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
data: [],
|
||||
status: 404,
|
||||
})
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
data: {
|
||||
streams: [
|
||||
{
|
||||
entries: [{ ts: '2019-02-01T10:27:37.498180581Z', line: 'hello' }],
|
||||
labels: '{label="value"}',
|
||||
},
|
||||
{
|
||||
entries: [{ ts: '2019-02-01T12:27:37.498180581Z', line: 'hello 2' }],
|
||||
labels: '{label2="value2"}',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
const query = makeAnnotationQueryRequest();
|
||||
|
||||
const res = await ds.annotationQuery(query);
|
||||
@ -400,7 +411,7 @@ describe('LokiDatasource', () => {
|
||||
});
|
||||
|
||||
describe('metricFindQuery', () => {
|
||||
const ds = new LokiDatasource(instanceSettings, backendSrv, templateSrvMock);
|
||||
const ds = new LokiDatasource(instanceSettings, templateSrvMock);
|
||||
const mocks = makeMetadataAndVersionsMocks();
|
||||
|
||||
mocks.forEach((mock, index) => {
|
||||
@ -456,21 +467,15 @@ type LimitTestArgs = {
|
||||
maxLines?: number;
|
||||
expectedLimit: number;
|
||||
};
|
||||
function makeLimitTest(
|
||||
instanceSettings: any,
|
||||
backendSrvMock: any,
|
||||
backendSrv: any,
|
||||
templateSrvMock: any,
|
||||
testResp: any
|
||||
) {
|
||||
function makeLimitTest(instanceSettings: any, datasourceRequestMock: any, templateSrvMock: any, testResp: any) {
|
||||
return ({ maxDataPoints, maxLines, expectedLimit }: LimitTestArgs) => {
|
||||
let settings = instanceSettings;
|
||||
if (Number.isFinite(maxLines)) {
|
||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||
settings = { ...instanceSettings, jsonData: customData };
|
||||
}
|
||||
const ds = new LokiDatasource(settings, backendSrv, templateSrvMock);
|
||||
backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
|
||||
const ds = new LokiDatasource(settings, templateSrvMock);
|
||||
datasourceRequestMock.mockImplementation(() => Promise.resolve(testResp));
|
||||
|
||||
const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
|
||||
if (Number.isFinite(maxDataPoints)) {
|
||||
@ -482,8 +487,8 @@ function makeLimitTest(
|
||||
|
||||
ds.query(options);
|
||||
|
||||
expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
|
||||
expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain(`limit=${expectedLimit}`);
|
||||
expect(datasourceRequestMock.mock.calls.length).toBe(1);
|
||||
expect(datasourceRequestMock.mock.calls[0][0].url).toContain(`limit=${expectedLimit}`);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,8 @@ import { map, filter, catchError, switchMap, mergeMap } from 'rxjs/operators';
|
||||
// Services & Utils
|
||||
import { dateMath } from '@grafana/data';
|
||||
import { addLabelToSelector, keepSelectorFilters } from 'app/plugins/datasource/prometheus/add_label_to_query';
|
||||
import { BackendSrv, DatasourceRequestOptions } from 'app/core/services/backend_srv';
|
||||
import { DatasourceRequestOptions } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { safeStringifyValue, convertToWebSocketUrl } from 'app/core/utils/explore';
|
||||
import {
|
||||
@ -84,11 +85,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
||||
maxLines: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private instanceSettings: DataSourceInstanceSettings<LokiOptions>,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv
|
||||
) {
|
||||
constructor(private instanceSettings: DataSourceInstanceSettings<LokiOptions>, private templateSrv: TemplateSrv) {
|
||||
super(instanceSettings);
|
||||
|
||||
this.languageProvider = new LanguageProvider(this);
|
||||
@ -122,7 +119,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
||||
url,
|
||||
};
|
||||
|
||||
return from(this.backendSrv.datasourceRequest(req));
|
||||
return from(getBackendSrv().datasourceRequest(req));
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<LokiQuery>): Observable<DataQueryResponse> {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import ResponseParser from './response_parser';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
//Types
|
||||
@ -13,12 +13,7 @@ export class MssqlDatasource {
|
||||
interval: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
instanceSettings: any,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv,
|
||||
private timeSrv: TimeSrv
|
||||
) {
|
||||
constructor(instanceSettings: any, private templateSrv: TemplateSrv, private timeSrv: TimeSrv) {
|
||||
this.name = instanceSettings.name;
|
||||
this.id = instanceSettings.id;
|
||||
this.responseParser = new ResponseParser();
|
||||
@ -81,7 +76,7 @@ export class MssqlDatasource {
|
||||
return Promise.resolve({ data: [] });
|
||||
}
|
||||
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
@ -106,7 +101,7 @@ export class MssqlDatasource {
|
||||
format: 'table',
|
||||
};
|
||||
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
@ -139,7 +134,7 @@ export class MssqlDatasource {
|
||||
to: range.to.valueOf().toString(),
|
||||
};
|
||||
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
@ -149,7 +144,7 @@ export class MssqlDatasource {
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
|
@ -4,19 +4,26 @@ import { CustomVariable } from 'app/features/templating/custom_variable';
|
||||
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
describe('MSSQLDatasource', () => {
|
||||
const templateSrv: TemplateSrv = new TemplateSrv();
|
||||
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||
|
||||
const ctx: any = {
|
||||
backendSrv: {},
|
||||
timeSrv: new TimeSrvStub(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.instanceSettings = { name: 'mssql' };
|
||||
jest.clearAllMocks();
|
||||
|
||||
ctx.ds = new MssqlDatasource(ctx.instanceSettings, ctx.backendSrv, templateSrv, ctx.timeSrv);
|
||||
ctx.instanceSettings = { name: 'mssql' };
|
||||
ctx.ds = new MssqlDatasource(ctx.instanceSettings, templateSrv, ctx.timeSrv);
|
||||
});
|
||||
|
||||
describe('When performing annotationQuery', () => {
|
||||
@ -54,9 +61,7 @@ describe('MSSQLDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 }));
|
||||
|
||||
return ctx.ds.annotationQuery(options).then((data: any) => {
|
||||
results = data;
|
||||
@ -102,9 +107,7 @@ describe('MSSQLDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 }));
|
||||
|
||||
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
||||
results = data;
|
||||
@ -143,9 +146,7 @@ describe('MSSQLDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 }));
|
||||
|
||||
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
||||
results = data;
|
||||
@ -186,10 +187,7 @@ describe('MSSQLDatasource', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
|
||||
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 }));
|
||||
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
||||
results = data;
|
||||
});
|
||||
@ -229,10 +227,10 @@ describe('MSSQLDatasource', () => {
|
||||
beforeEach(() => {
|
||||
ctx.timeSrv.setTime(time);
|
||||
|
||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
||||
datasourceRequestMock.mockImplementation((options: any) => {
|
||||
results = options.data;
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
};
|
||||
});
|
||||
|
||||
return ctx.ds.metricFindQuery(query);
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import ResponseParser from './response_parser';
|
||||
import MysqlQuery from 'app/plugins/datasource/mysql/mysql_query';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
//Types
|
||||
@ -16,12 +16,7 @@ export class MysqlDatasource {
|
||||
interval: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
instanceSettings: any,
|
||||
private backendSrv: BackendSrv,
|
||||
private templateSrv: TemplateSrv,
|
||||
private timeSrv: TimeSrv
|
||||
) {
|
||||
constructor(instanceSettings: any, private templateSrv: TemplateSrv, private timeSrv: TimeSrv) {
|
||||
this.name = instanceSettings.name;
|
||||
this.id = instanceSettings.id;
|
||||
this.responseParser = new ResponseParser();
|
||||
@ -83,7 +78,7 @@ export class MysqlDatasource {
|
||||
return Promise.resolve({ data: [] });
|
||||
}
|
||||
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
@ -110,7 +105,7 @@ export class MysqlDatasource {
|
||||
format: 'table',
|
||||
};
|
||||
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
@ -156,7 +151,7 @@ export class MysqlDatasource {
|
||||
data['to'] = optionalOptions.range.to.valueOf().toString();
|
||||
}
|
||||
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
@ -166,7 +161,7 @@ export class MysqlDatasource {
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
return this.backendSrv
|
||||
return getBackendSrv()
|
||||
.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user