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:
committed by
Hugo Häggmark
parent
6ff315a299
commit
cf2cc71393
@@ -259,7 +259,7 @@
|
|||||||
"redux-thunk": "2.3.0",
|
"redux-thunk": "2.3.0",
|
||||||
"reselect": "4.0.0",
|
"reselect": "4.0.0",
|
||||||
"rst2html": "github:thoward/rst2html#990cb89",
|
"rst2html": "github:thoward/rst2html#990cb89",
|
||||||
"rxjs": "6.4.0",
|
"rxjs": "6.5.4",
|
||||||
"search-query-parser": "1.5.2",
|
"search-query-parser": "1.5.2",
|
||||||
"slate": "0.47.8",
|
"slate": "0.47.8",
|
||||||
"slate-plain-serializer": "0.7.10",
|
"slate-plain-serializer": "0.7.10",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"rollup-plugin-terser": "4.0.4",
|
"rollup-plugin-terser": "4.0.4",
|
||||||
"rollup-plugin-typescript2": "0.19.3",
|
"rollup-plugin-typescript2": "0.19.3",
|
||||||
"rollup-plugin-visualizer": "0.9.2",
|
"rollup-plugin-visualizer": "0.9.2",
|
||||||
"rxjs": "6.4.0",
|
"rxjs": "6.5.4",
|
||||||
"sinon": "1.17.6",
|
"sinon": "1.17.6",
|
||||||
"typescript": "3.7.2"
|
"typescript": "3.7.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,8 @@ export interface BackendSrv {
|
|||||||
|
|
||||||
let singletonInstance: BackendSrv;
|
let singletonInstance: BackendSrv;
|
||||||
|
|
||||||
export function setBackendSrv(instance: BackendSrv) {
|
export const setBackendSrv = (instance: BackendSrv) => {
|
||||||
singletonInstance = instance;
|
singletonInstance = instance;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function getBackendSrv(): BackendSrv {
|
export const getBackendSrv = (): BackendSrv => singletonInstance;
|
||||||
return singletonInstance;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import { AsyncSelect } from '@grafana/ui';
|
import { AsyncSelect } from '@grafana/ui';
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { debounce } from 'lodash';
|
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';
|
import { DashboardSearchHit, DashboardDTO } from 'app/types';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -33,20 +33,16 @@ export class DashboardPicker extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
getDashboards = (query = '') => {
|
getDashboards = (query = '') => {
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
return getBackendSrv()
|
return backendSrv.search({ type: 'dash-db', query }).then((result: DashboardSearchHit[]) => {
|
||||||
.search({ type: 'dash-db', query })
|
const dashboards = result.map((item: DashboardSearchHit) => ({
|
||||||
.then((result: DashboardSearchHit[]) => {
|
id: item.id,
|
||||||
const dashboards = result.map((item: DashboardSearchHit) => {
|
value: item.id,
|
||||||
return {
|
label: `${item.folderTitle ? item.folderTitle : 'General'}/${item.title}`,
|
||||||
id: item.id,
|
}));
|
||||||
value: item.id,
|
|
||||||
label: `${item.folderTitle ? item.folderTitle : 'General'}/${item.title}`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({ isLoading: false });
|
this.setState({ isLoading: false });
|
||||||
return dashboards;
|
return dashboards;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { TeamPicker } from './TeamPicker';
|
import { TeamPicker } from './TeamPicker';
|
||||||
|
|
||||||
jest.mock('app/core/services/backend_srv', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
getBackendSrv: () => {
|
getBackendSrv: () => {
|
||||||
return {
|
return {
|
||||||
get: () => {
|
get: () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { AsyncSelect } from '@grafana/ui';
|
import { AsyncSelect } from '@grafana/ui';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export interface Team {
|
export interface Team {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -35,27 +35,28 @@ export class TeamPicker extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
search(query?: string) {
|
search(query?: string) {
|
||||||
const backendSrv = getBackendSrv();
|
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
|
|
||||||
if (_.isNil(query)) {
|
if (_.isNil(query)) {
|
||||||
query = '';
|
query = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return backendSrv.get(`/api/teams/search?perpage=100&page=1&query=${query}`).then((result: any) => {
|
return getBackendSrv()
|
||||||
const teams = result.teams.map((team: any) => {
|
.get(`/api/teams/search?perpage=100&page=1&query=${query}`)
|
||||||
return {
|
.then((result: any) => {
|
||||||
id: team.id,
|
const teams = result.teams.map((team: any) => {
|
||||||
value: team.id,
|
return {
|
||||||
label: team.name,
|
id: team.id,
|
||||||
name: team.name,
|
value: team.id,
|
||||||
imgUrl: team.avatarUrl,
|
label: team.name,
|
||||||
};
|
name: team.name,
|
||||||
});
|
imgUrl: team.avatarUrl,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
this.setState({ isLoading: false });
|
this.setState({ isLoading: false });
|
||||||
return teams;
|
return teams;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -3,14 +3,8 @@ import React from 'react';
|
|||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { UserPicker } from './UserPicker';
|
import { UserPicker } from './UserPicker';
|
||||||
|
|
||||||
jest.mock('app/core/services/backend_srv', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
getBackendSrv: () => {
|
getBackendSrv: () => ({ get: jest.fn().mockResolvedValue([]) }),
|
||||||
return {
|
|
||||||
get: () => {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('UserPicker', () => {
|
describe('UserPicker', () => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { AsyncSelect } from '@grafana/ui';
|
|||||||
|
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { User } from 'app/types';
|
import { User } from 'app/types';
|
||||||
@@ -36,14 +36,13 @@ export class UserPicker extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
search(query?: string) {
|
search(query?: string) {
|
||||||
const backendSrv = getBackendSrv();
|
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
|
|
||||||
if (_.isNil(query)) {
|
if (_.isNil(query)) {
|
||||||
query = '';
|
query = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return backendSrv
|
return getBackendSrv()
|
||||||
.get(`/api/org/users/lookup?query=${query}&limit=10`)
|
.get(`/api/org/users/lookup?query=${query}&limit=10`)
|
||||||
.then((result: any) => {
|
.then((result: any) => {
|
||||||
return result.map((user: any) => ({
|
return result.map((user: any) => ({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import { FormLabel, Select } from '@grafana/ui';
|
import { FormLabel, Select } from '@grafana/ui';
|
||||||
|
|
||||||
import { DashboardSearchHit, DashboardSearchHitType } from 'app/types';
|
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 {
|
export interface Props {
|
||||||
resourceUri: string;
|
resourceUri: string;
|
||||||
@@ -29,7 +29,7 @@ const timezones = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export class SharedPreferences extends PureComponent<Props, State> {
|
export class SharedPreferences extends PureComponent<Props, State> {
|
||||||
backendSrv = getBackendSrv();
|
backendSrv = backendSrv;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -43,8 +43,8 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const prefs = await this.backendSrv.get(`/api/${this.props.resourceUri}/preferences`);
|
const prefs = await backendSrv.get(`/api/${this.props.resourceUri}/preferences`);
|
||||||
const dashboards = await this.backendSrv.search({ starred: true });
|
const dashboards = await backendSrv.search({ starred: true });
|
||||||
const defaultDashboardHit: DashboardSearchHit = {
|
const defaultDashboardHit: DashboardSearchHit = {
|
||||||
id: 0,
|
id: 0,
|
||||||
title: 'Default',
|
title: 'Default',
|
||||||
@@ -62,7 +62,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (prefs.homeDashboardId > 0 && !dashboards.find(d => d.id === prefs.homeDashboardId)) {
|
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) {
|
if (missing && missing.length > 0) {
|
||||||
dashboards.push(missing[0]);
|
dashboards.push(missing[0]);
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
const { homeDashboardId, theme, timezone } = this.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,
|
homeDashboardId,
|
||||||
theme,
|
theme,
|
||||||
timezone,
|
timezone,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import { BackendSrv } from '../services/backend_srv';
|
import { backendSrv } from '../services/backend_srv';
|
||||||
|
|
||||||
const template = `
|
const template = `
|
||||||
<select class="gf-form-input" ng-model="ctrl.model" ng-options="f.value as f.text for f in ctrl.options"></select>
|
<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;
|
model: any;
|
||||||
options: any;
|
options: any;
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
constructor(private backendSrv: BackendSrv) {}
|
|
||||||
|
|
||||||
$onInit() {
|
$onInit() {
|
||||||
this.options = [{ value: 0, text: 'Default' }];
|
this.options = [{ value: 0, text: 'Default' }];
|
||||||
|
|
||||||
return this.backendSrv.search({ starred: true }).then(res => {
|
return backendSrv.search({ starred: true }).then(res => {
|
||||||
res.forEach(dash => {
|
res.forEach(dash => {
|
||||||
this.options.push({ value: dash.id, text: dash.title });
|
this.options.push({ value: dash.id, text: dash.title });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import _ from 'lodash';
|
|||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { SearchSrv } from 'app/core/services/search_srv';
|
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 { ContextSrv } from 'app/core/services/context_srv';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
import { promiseToDigest } from '../../utils/promiseToDigest';
|
||||||
|
|
||||||
export interface Section {
|
export interface Section {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -69,12 +70,7 @@ export class ManageDashboardsCtrl {
|
|||||||
hasEditPermissionInFolders: boolean;
|
hasEditPermissionInFolders: boolean;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(private $scope: IScope, private searchSrv: SearchSrv, private contextSrv: ContextSrv) {
|
||||||
private $scope: IScope,
|
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private searchSrv: SearchSrv,
|
|
||||||
private contextSrv: ContextSrv
|
|
||||||
) {
|
|
||||||
this.isEditor = this.contextSrv.isEditor;
|
this.isEditor = this.contextSrv.isEditor;
|
||||||
this.hasEditPermissionInFolders = this.contextSrv.hasEditPermissionInFolders;
|
this.hasEditPermissionInFolders = this.contextSrv.hasEditPermissionInFolders;
|
||||||
|
|
||||||
@@ -108,10 +104,10 @@ export class ManageDashboardsCtrl {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
if (!this.folderUid) {
|
if (!this.folderUid) {
|
||||||
this.$scope.$digest();
|
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;
|
this.canSave = folder.canSave;
|
||||||
if (!this.canSave) {
|
if (!this.canSave) {
|
||||||
this.hasEditPermissionInFolders = false;
|
this.hasEditPermissionInFolders = false;
|
||||||
@@ -216,9 +212,11 @@ export class ManageDashboardsCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private deleteFoldersAndDashboards(folderUids: string[], dashboardUids: string[]) {
|
private deleteFoldersAndDashboards(folderUids: string[], dashboardUids: string[]) {
|
||||||
this.backendSrv.deleteFoldersAndDashboards(folderUids, dashboardUids).then(() => {
|
promiseToDigest(this.$scope)(
|
||||||
this.refreshList();
|
backendSrv.deleteFoldersAndDashboards(folderUids, dashboardUids).then(() => {
|
||||||
});
|
this.refreshList();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDashboardsToMove() {
|
getDashboardsToMove() {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { ILocationService, IScope } from 'angular';
|
||||||
import { e2e } from '@grafana/e2e';
|
import { e2e } from '@grafana/e2e';
|
||||||
|
|
||||||
import coreModule from '../../core_module';
|
import coreModule from '../../core_module';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
import { promiseToDigest } from '../../utils/promiseToDigest';
|
||||||
|
|
||||||
export class SearchResultsCtrl {
|
export class SearchResultsCtrl {
|
||||||
results: any;
|
results: any;
|
||||||
@@ -14,7 +16,7 @@ export class SearchResultsCtrl {
|
|||||||
selectors: typeof e2e.pages.Dashboards.selectors;
|
selectors: typeof e2e.pages.Dashboards.selectors;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $location: any) {
|
constructor(private $location: ILocationService, private $scope: IScope) {
|
||||||
this.selectors = e2e.pages.Dashboards.selectors;
|
this.selectors = e2e.pages.Dashboards.selectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,19 +26,21 @@ export class SearchResultsCtrl {
|
|||||||
this.onFolderExpanding();
|
this.onFolderExpanding();
|
||||||
}
|
}
|
||||||
|
|
||||||
section.toggle(section).then((f: any) => {
|
promiseToDigest(this.$scope)(
|
||||||
if (this.editable && f.expanded) {
|
section.toggle(section).then((f: any) => {
|
||||||
if (f.items) {
|
if (this.editable && f.expanded) {
|
||||||
_.each(f.items, i => {
|
if (f.items) {
|
||||||
i.checked = f.checked;
|
_.each(f.items, i => {
|
||||||
});
|
i.checked = f.checked;
|
||||||
|
});
|
||||||
|
|
||||||
if (this.onSelectionChanged) {
|
if (this.onSelectionChanged) {
|
||||||
this.onSelectionChanged();
|
this.onSelectionChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import coreModule from '../core_module';
|
import coreModule from '../core_module';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
import { promiseToDigest } from '../utils/promiseToDigest';
|
||||||
|
|
||||||
export class InvitedCtrl {
|
export class InvitedCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope: any, $routeParams: any, contextSrv: any, backendSrv: any) {
|
constructor($scope: any, $routeParams: any, contextSrv: any) {
|
||||||
contextSrv.sidemenu = false;
|
contextSrv.sidemenu = false;
|
||||||
$scope.formModel = {};
|
$scope.formModel = {};
|
||||||
|
|
||||||
@@ -17,15 +19,19 @@ export class InvitedCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.init = () => {
|
$scope.init = () => {
|
||||||
backendSrv.get('/api/user/invite/' + $routeParams.code).then((invite: any) => {
|
promiseToDigest($scope)(
|
||||||
$scope.formModel.name = invite.name;
|
getBackendSrv()
|
||||||
$scope.formModel.email = invite.email;
|
.get('/api/user/invite/' + $routeParams.code)
|
||||||
$scope.formModel.username = invite.email;
|
.then((invite: any) => {
|
||||||
$scope.formModel.inviteCode = $routeParams.code;
|
$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.greeting = invite.name || invite.email || invite.username;
|
||||||
$scope.invitedBy = invite.invitedBy;
|
$scope.invitedBy = invite.invitedBy;
|
||||||
});
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.submit = () => {
|
$scope.submit = () => {
|
||||||
@@ -33,9 +39,11 @@ export class InvitedCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backendSrv.post('/api/user/invite/complete', $scope.formModel).then(() => {
|
getBackendSrv()
|
||||||
window.location.href = config.appSubUrl + '/';
|
.post('/api/user/invite/complete', $scope.formModel)
|
||||||
});
|
.then(() => {
|
||||||
|
window.location.href = config.appSubUrl + '/';
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.init();
|
$scope.init();
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import coreModule from '../core_module';
|
import coreModule from '../core_module';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { BackendSrv } from '../services/backend_srv';
|
|
||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
import { promiseToDigest } from '../utils/promiseToDigest';
|
||||||
|
|
||||||
export class ResetPasswordCtrl {
|
export class ResetPasswordCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope: any, backendSrv: BackendSrv, $location: any) {
|
constructor($scope: any, $location: any) {
|
||||||
$scope.formModel = {};
|
$scope.formModel = {};
|
||||||
$scope.mode = 'send';
|
$scope.mode = 'send';
|
||||||
$scope.ldapEnabled = config.ldapEnabled;
|
$scope.ldapEnabled = config.ldapEnabled;
|
||||||
@@ -31,9 +32,14 @@ export class ResetPasswordCtrl {
|
|||||||
if (!$scope.sendResetForm.$valid) {
|
if (!$scope.sendResetForm.$valid) {
|
||||||
return;
|
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 = () => {
|
$scope.submitReset = () => {
|
||||||
@@ -46,9 +52,11 @@ export class ResetPasswordCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backendSrv.post('/api/user/password/reset', $scope.formModel).then(() => {
|
getBackendSrv()
|
||||||
$location.path('login');
|
.post('/api/user/password/reset', $scope.formModel)
|
||||||
});
|
.then(() => {
|
||||||
|
$location.path('login');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import coreModule from '../core_module';
|
import coreModule from '../core_module';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime/src/services';
|
||||||
|
import { promiseToDigest } from '../utils/promiseToDigest';
|
||||||
|
|
||||||
export class SignUpCtrl {
|
export class SignUpCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $scope: any, private backendSrv: any, $location: any, contextSrv: any) {
|
constructor(private $scope: any, $location: any, contextSrv: any) {
|
||||||
contextSrv.sidemenu = false;
|
contextSrv.sidemenu = false;
|
||||||
$scope.ctrl = this;
|
$scope.ctrl = this;
|
||||||
|
|
||||||
@@ -34,10 +36,14 @@ export class SignUpCtrl {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
backendSrv.get('/api/user/signup/options').then((options: any) => {
|
promiseToDigest($scope)(
|
||||||
$scope.verifyEmailEnabled = options.verifyEmailEnabled;
|
getBackendSrv()
|
||||||
$scope.autoAssignOrg = options.autoAssignOrg;
|
.get('/api/user/signup/options')
|
||||||
});
|
.then((options: any) => {
|
||||||
|
$scope.verifyEmailEnabled = options.verifyEmailEnabled;
|
||||||
|
$scope.autoAssignOrg = options.autoAssignOrg;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
@@ -45,13 +51,15 @@ export class SignUpCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.backendSrv.post('/api/user/signup/step2', this.$scope.formModel).then((rsp: any) => {
|
getBackendSrv()
|
||||||
if (rsp.code === 'redirect-to-select-org') {
|
.post('/api/user/signup/step2', this.$scope.formModel)
|
||||||
window.location.href = config.appSubUrl + '/profile/select-org?signup=1';
|
.then((rsp: any) => {
|
||||||
} else {
|
if (rsp.code === 'redirect-to-select-org') {
|
||||||
window.location.href = config.appSubUrl + '/';
|
window.location.href = config.appSubUrl + '/profile/select-org?signup=1';
|
||||||
}
|
} else {
|
||||||
});
|
window.location.href = config.appSubUrl + '/';
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,119 @@
|
|||||||
import _ from 'lodash';
|
import omitBy from 'lodash/omitBy';
|
||||||
import angular from 'angular';
|
import { from, merge, MonoTypeOperatorFunction, Observable, Subject, throwError } from 'rxjs';
|
||||||
import coreModule from 'app/core/core_module';
|
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 appEvents from 'app/core/app_events';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||||
import { DashboardSearchHit } from 'app/types/search';
|
import { DashboardSearchHit } from 'app/types/search';
|
||||||
import { ContextSrv } from './context_srv';
|
import { CoreEvents, DashboardDTO, FolderInfo } from 'app/types';
|
||||||
import { FolderInfo, DashboardDTO, CoreEvents } from 'app/types';
|
import { ContextSrv, contextSrv } from './context_srv';
|
||||||
import { BackendSrv as BackendService, getBackendSrv as getBackendService, BackendSrvRequest } from '@grafana/runtime';
|
import { coreModule } from 'app/core/core_module';
|
||||||
import { AppEvents } from '@grafana/data';
|
import { Emitter } from '../utils/emitter';
|
||||||
|
|
||||||
export interface DatasourceRequestOptions {
|
export interface DatasourceRequestOptions {
|
||||||
retry?: number;
|
retry?: number;
|
||||||
method?: string;
|
method?: string;
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
timeout?: angular.IPromise<any>;
|
timeout?: Promise<any>;
|
||||||
url?: string;
|
url?: string;
|
||||||
headers?: { [key: string]: any };
|
headers?: Record<string, any>;
|
||||||
silent?: boolean;
|
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 {
|
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 HTTP_REQUEST_CANCELED = -1;
|
||||||
private noBackendCache: boolean;
|
private noBackendCache: boolean;
|
||||||
|
private dependencies: BackendSrvDependencies = {
|
||||||
|
fromFetch: fromFetch,
|
||||||
|
appEvents: appEvents,
|
||||||
|
contextSrv: contextSrv,
|
||||||
|
logout: () => {
|
||||||
|
window.location.href = config.appSubUrl + '/logout';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/** @ngInject */
|
constructor(deps?: BackendSrvDependencies) {
|
||||||
constructor(
|
if (deps) {
|
||||||
private $http: any,
|
this.dependencies = {
|
||||||
private $q: angular.IQService,
|
...this.dependencies,
|
||||||
private $timeout: angular.ITimeoutService,
|
...deps,
|
||||||
private contextSrv: ContextSrv
|
};
|
||||||
) {}
|
}
|
||||||
|
|
||||||
get(url: string, params?: any) {
|
|
||||||
return this.request({ method: 'GET', url, params });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(url: string) {
|
async get(url: string, params?: any) {
|
||||||
return this.request({ method: 'DELETE', url });
|
return await this.request({ method: 'GET', url, params });
|
||||||
}
|
}
|
||||||
|
|
||||||
post(url: string, data?: any) {
|
async delete(url: string) {
|
||||||
return this.request({ method: 'POST', url, data });
|
return await this.request({ method: 'DELETE', url });
|
||||||
}
|
}
|
||||||
|
|
||||||
patch(url: string, data: any) {
|
async post(url: string, data?: any) {
|
||||||
return this.request({ method: 'PATCH', url, data });
|
return await this.request({ method: 'POST', url, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
put(url: string, data: any) {
|
async patch(url: string, data: any) {
|
||||||
return this.request({ method: 'PUT', url, data });
|
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) {
|
withNoBackendCache(callback: any) {
|
||||||
@@ -61,18 +123,18 @@ export class BackendSrv implements BackendService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
requestErrorHandler(err: any) {
|
requestErrorHandler = (err: ErrorResponse) => {
|
||||||
if (err.isHandled) {
|
if (err.isHandled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = err.data || { message: 'Unexpected error' };
|
let data = err.data ?? { message: 'Unexpected error' };
|
||||||
if (_.isString(data)) {
|
if (typeof data === 'string') {
|
||||||
data = { message: data };
|
data = { message: data };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.status === 422) {
|
if (err.status === 422) {
|
||||||
appEvents.emit(AppEvents.alertWarning, ['Validation failed', data.message]);
|
this.dependencies.appEvents.emit(AppEvents.alertWarning, ['Validation failed', data.message]);
|
||||||
throw data;
|
throw data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,170 +146,133 @@ export class BackendSrv implements BackendService {
|
|||||||
message = 'Error';
|
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;
|
throw data;
|
||||||
}
|
};
|
||||||
|
|
||||||
request(options: BackendSrvRequest) {
|
async request(options: BackendSrvRequest): Promise<any> {
|
||||||
options.retry = options.retry || 0;
|
options = this.parseRequestOptions(options, this.dependencies.contextSrv.user?.orgId);
|
||||||
const requestIsLocal = !options.url.match(/^http/);
|
|
||||||
const firstAttempt = options.retry === 0;
|
|
||||||
|
|
||||||
if (requestIsLocal) {
|
const fromFetchStream = this.getFromFetchStream(options);
|
||||||
if (this.contextSrv.user && this.contextSrv.user.orgId) {
|
const failureStream = fromFetchStream.pipe(this.toFailureStream(options));
|
||||||
options.headers = options.headers || {};
|
const successStream = fromFetchStream.pipe(
|
||||||
options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId;
|
filter(response => response.ok === true),
|
||||||
}
|
map(response => {
|
||||||
|
const fetchSuccessResponse: SuccessResponse = response.data;
|
||||||
if (options.url.indexOf('/') === 0) {
|
return fetchSuccessResponse;
|
||||||
options.url = options.url.substring(1);
|
}),
|
||||||
}
|
tap(response => {
|
||||||
}
|
if (options.method !== 'GET' && response?.message && options.showSuccessAlert !== false) {
|
||||||
|
this.dependencies.appEvents.emit(AppEvents.alertSuccess, [response.message]);
|
||||||
return this.$http(options).then(
|
|
||||||
(results: any) => {
|
|
||||||
if (options.method !== 'GET') {
|
|
||||||
if (results && results.data.message) {
|
|
||||||
if (options.showSuccessAlert !== false) {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, [results.data.message]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return results.data;
|
})
|
||||||
},
|
|
||||||
(err: any) => {
|
|
||||||
// handle unauthorized
|
|
||||||
if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) {
|
|
||||||
return this.loginPing()
|
|
||||||
.then(() => {
|
|
||||||
options.retry = 1;
|
|
||||||
return this.request(options);
|
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
if (err.status === 401) {
|
|
||||||
window.location.href = config.appSubUrl + '/logout';
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$timeout(this.requestErrorHandler.bind(this, err), 50);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
addCanceler(requestId: string, canceler: angular.IDeferred<any>) {
|
return merge(successStream, failureStream)
|
||||||
if (requestId in this.inFlightRequests) {
|
.pipe(
|
||||||
this.inFlightRequests[requestId].push(canceler);
|
catchError((err: ErrorResponse) => {
|
||||||
} else {
|
if (err.status === 401) {
|
||||||
this.inFlightRequests[requestId] = [canceler];
|
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) {
|
resolveCancelerIfExists(requestId: string) {
|
||||||
const cancelers = this.inFlightRequests[requestId];
|
this.inFlightRequests.next(requestId);
|
||||||
if (!_.isUndefined(cancelers) && cancelers.length) {
|
|
||||||
cancelers[0].resolve();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
datasourceRequest(options: BackendSrvRequest) {
|
async datasourceRequest(options: BackendSrvRequest): Promise<any> {
|
||||||
let canceler: angular.IDeferred<any> = null;
|
// A requestId is provided by the datasource as a unique identifier for a
|
||||||
options.retry = options.retry || 0;
|
// 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
|
||||||
// A requestID is provided by the datasource as a unique identifier for a
|
if (options.requestId) {
|
||||||
// particular query. If the requestID exists, the promise it is keyed to
|
this.inFlightRequests.next(options.requestId);
|
||||||
// is canceled, canceling the previous datasource request if it is still
|
|
||||||
// in-flight.
|
|
||||||
const requestId = options.requestId;
|
|
||||||
|
|
||||||
if (requestId) {
|
|
||||||
this.resolveCancelerIfExists(requestId);
|
|
||||||
// create new canceler
|
|
||||||
canceler = this.$q.defer();
|
|
||||||
options.timeout = canceler.promise;
|
|
||||||
this.addCanceler(requestId, canceler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestIsLocal = !options.url.match(/^http/);
|
options = this.parseDataSourceRequestOptions(
|
||||||
const firstAttempt = options.retry === 0;
|
options,
|
||||||
|
this.dependencies.contextSrv.user?.orgId,
|
||||||
|
this.noBackendCache
|
||||||
|
);
|
||||||
|
|
||||||
if (requestIsLocal) {
|
const fromFetchStream = this.getFromFetchStream(options);
|
||||||
if (this.contextSrv.user && this.contextSrv.user.orgId) {
|
const failureStream = fromFetchStream.pipe(this.toFailureStream(options));
|
||||||
options.headers = options.headers || {};
|
const successStream = fromFetchStream.pipe(
|
||||||
options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId;
|
filter(response => response.ok === true),
|
||||||
}
|
map(response => {
|
||||||
|
const { data } = response;
|
||||||
if (options.url.indexOf('/') === 0) {
|
const fetchSuccessResponse: DataSourceSuccessResponse = { data };
|
||||||
options.url = options.url.substring(1);
|
return fetchSuccessResponse;
|
||||||
}
|
}),
|
||||||
|
tap(res => {
|
||||||
if (options.headers && options.headers.Authorization) {
|
|
||||||
options.headers['X-DS-Authorization'] = options.headers.Authorization;
|
|
||||||
delete options.headers.Authorization;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.noBackendCache) {
|
|
||||||
options.headers['X-Grafana-NoCache'] = 'true';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.$http(options)
|
|
||||||
.then((response: any) => {
|
|
||||||
if (!options.silent) {
|
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
|
return merge(successStream, failureStream)
|
||||||
if (requestIsLocal && firstAttempt && err.status === 401) {
|
.pipe(
|
||||||
return this.loginPing()
|
catchError((err: ErrorResponse) => {
|
||||||
.then(() => {
|
if (err.status === this.HTTP_REQUEST_CANCELED) {
|
||||||
options.retry = 1;
|
return throwError({
|
||||||
if (canceler) {
|
err,
|
||||||
canceler.resolve();
|
cancelled: true,
|
||||||
}
|
|
||||||
return this.datasourceRequest(options);
|
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
if (err.status === 401) {
|
|
||||||
window.location.href = config.appSubUrl + '/logout';
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate error obj on Internal Error
|
if (err.status === 401) {
|
||||||
if (_.isString(err.data) && err.status === 500) {
|
this.dependencies.logout();
|
||||||
err.data = {
|
return throwError(err);
|
||||||
error: err.statusText,
|
}
|
||||||
response: err.data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// for Prometheus
|
// populate error obj on Internal Error
|
||||||
if (err.data && !err.data.message && _.isString(err.data.error)) {
|
if (typeof err.data === 'string' && err.status === 500) {
|
||||||
err.data.message = err.data.error;
|
err.data = {
|
||||||
}
|
error: err.statusText,
|
||||||
if (!options.silent) {
|
response: err.data,
|
||||||
appEvents.emit(CoreEvents.dsRequestError, err);
|
};
|
||||||
}
|
}
|
||||||
throw err;
|
|
||||||
})
|
// for Prometheus
|
||||||
.finally(() => {
|
if (err.data && !err.data.message && typeof err.data.error === 'string') {
|
||||||
// clean up
|
err.data.message = err.data.error;
|
||||||
if (options.requestId) {
|
}
|
||||||
this.inFlightRequests[options.requestId].shift();
|
|
||||||
}
|
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() {
|
loginPing() {
|
||||||
@@ -309,7 +334,7 @@ export class BackendSrv implements BackendService {
|
|||||||
tasks.push(this.createTask(this.deleteDashboard.bind(this), true, dashboardUid, true));
|
tasks.push(this.createTask(this.deleteDashboard.bind(this), true, dashboardUid, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.executeInOrder(tasks, []);
|
return this.executeInOrder(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveDashboards(dashboardUids: string[], toFolder: FolderInfo) {
|
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));
|
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 {
|
return {
|
||||||
totalCount: result.length,
|
totalCount: result.length,
|
||||||
successCount: _.filter(result, { succeeded: true }).length,
|
successCount: result.filter((res: any) => res.succeeded).length,
|
||||||
alreadyInFolderCount: _.filter(result, { alreadyInFolder: true }).length,
|
alreadyInFolderCount: result.filter((res: any) => res.alreadyInFolder).length,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private moveDashboard(uid: string, toFolder: FolderInfo) {
|
private async moveDashboard(uid: string, toFolder: FolderInfo) {
|
||||||
const deferred = this.$q.defer();
|
const fullDash: DashboardDTO = await this.getDashboardByUid(uid);
|
||||||
|
const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
|
||||||
|
|
||||||
this.getDashboardByUid(uid).then((fullDash: DashboardDTO) => {
|
if ((!fullDash.meta.folderId && toFolder.id === 0) || fullDash.meta.folderId === toFolder.id) {
|
||||||
const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
|
return { alreadyInFolder: true };
|
||||||
|
}
|
||||||
|
|
||||||
if ((!fullDash.meta.folderId && toFolder.id === 0) || fullDash.meta.folderId === toFolder.id) {
|
const clone = model.getSaveModelClone();
|
||||||
deferred.resolve({ alreadyInFolder: true });
|
const options = {
|
||||||
return;
|
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();
|
err.isHandled = true;
|
||||||
const options = {
|
options.overwrite = true;
|
||||||
folderId: toFolder.id,
|
|
||||||
overwrite: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.saveDashboard(clone, options)
|
try {
|
||||||
.then(() => {
|
await this.saveDashboard(clone, options);
|
||||||
deferred.resolve({ succeeded: true });
|
return { succeeded: true };
|
||||||
})
|
} catch (e) {
|
||||||
.catch((err: any) => {
|
return { succeeded: false };
|
||||||
if (err.data && err.data.status === 'plugin-dashboard') {
|
}
|
||||||
err.isHandled = true;
|
}
|
||||||
options.overwrite = true;
|
|
||||||
|
|
||||||
this.saveDashboard(clone, options)
|
|
||||||
.then(() => {
|
|
||||||
deferred.resolve({ succeeded: true });
|
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
deferred.resolve({ succeeded: false });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
deferred.resolve({ succeeded: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTask(fn: Function, ignoreRejections: boolean, ...args: any[]) {
|
private createTask(fn: (...args: any[]) => Promise<any>, ignoreRejections: boolean, ...args: any[]) {
|
||||||
return (result: any) => {
|
return async (result: any) => {
|
||||||
return fn
|
try {
|
||||||
.apply(null, args)
|
const res = await fn(...args);
|
||||||
.then((res: any) => {
|
return Array.prototype.concat(result, [res]);
|
||||||
return Array.prototype.concat(result, [res]);
|
} catch (err) {
|
||||||
})
|
if (ignoreRejections) {
|
||||||
.catch((err: any) => {
|
return result;
|
||||||
if (ignoreRejections) {
|
}
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeInOrder(tasks: any[], initialValue: any[]) {
|
private executeInOrder(tasks: any[]) {
|
||||||
return tasks.reduce(this.$q.when, initialValue);
|
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
|
// Used for testing and things that really need BackendSrv
|
||||||
export function getBackendSrv(): BackendSrv {
|
export const backendSrv = new BackendSrv();
|
||||||
return getBackendService() as 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
|
// TODO: Enable backend request when we have metrics API
|
||||||
// if (this.options.url) {
|
// 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 impressionSrv from 'app/core/services/impression_srv';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
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 { Section } from '../components/manage_dashboards/manage_dashboards';
|
||||||
import { DashboardSearchHit } from 'app/types/search';
|
import { DashboardSearchHit } from 'app/types/search';
|
||||||
|
|
||||||
@@ -16,8 +16,7 @@ export class SearchSrv {
|
|||||||
recentIsOpen: boolean;
|
recentIsOpen: boolean;
|
||||||
starredIsOpen: boolean;
|
starredIsOpen: boolean;
|
||||||
|
|
||||||
/** @ngInject */
|
constructor() {
|
||||||
constructor(private backendSrv: BackendSrv) {
|
|
||||||
this.recentIsOpen = store.getBool('search.sections.recent', true);
|
this.recentIsOpen = store.getBool('search.sections.recent', true);
|
||||||
this.starredIsOpen = store.getBool('search.sections.starred', true);
|
this.starredIsOpen = store.getBool('search.sections.starred', true);
|
||||||
}
|
}
|
||||||
@@ -44,7 +43,7 @@ export class SearchSrv {
|
|||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv.search({ dashboardIds: dashIds }).then(result => {
|
return backendSrv.search({ dashboardIds: dashIds }).then(result => {
|
||||||
return dashIds
|
return dashIds
|
||||||
.map(orderId => {
|
.map(orderId => {
|
||||||
return _.find(result, { id: orderId });
|
return _.find(result, { id: orderId });
|
||||||
@@ -78,7 +77,7 @@ export class SearchSrv {
|
|||||||
return Promise.resolve();
|
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) {
|
if (result.length > 0) {
|
||||||
sections['starred'] = {
|
sections['starred'] = {
|
||||||
title: 'Starred',
|
title: 'Starred',
|
||||||
@@ -116,7 +115,7 @@ export class SearchSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.backendSrv.search(query).then(results => {
|
backendSrv.search(query).then(results => {
|
||||||
return this.handleSearchResult(sections, results);
|
return this.handleSearchResult(sections, results);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -197,14 +196,14 @@ export class SearchSrv {
|
|||||||
folderIds: [section.id],
|
folderIds: [section.id],
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.backendSrv.search(query).then(results => {
|
return backendSrv.search(query).then(results => {
|
||||||
section.items = results;
|
section.items = results;
|
||||||
return Promise.resolve(section);
|
return Promise.resolve(section);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDashboardTags() {
|
getDashboardTags() {
|
||||||
return this.backendSrv.get('/api/dashboards/tags');
|
return backendSrv.get('/api/dashboards/tags');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
// @ts-ignore
|
|
||||||
import { getBackendSrv } from '@grafana/runtime/src/services/backendSrv';
|
|
||||||
import { OrgSwitcher } from '../components/OrgSwitcher';
|
import { OrgSwitcher } from '../components/OrgSwitcher';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { OrgRole } from '@grafana/data';
|
import { OrgRole } from '@grafana/data';
|
||||||
|
|
||||||
const getMock = jest.fn(() => Promise.resolve([]));
|
const postMock = jest.fn().mockImplementation(jest.fn());
|
||||||
const postMock = jest.fn();
|
|
||||||
|
|
||||||
jest.mock('@grafana/runtime/src/services/backendSrv', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
getBackendSrv: () => ({
|
getBackendSrv: () => ({
|
||||||
get: getMock,
|
get: jest.fn().mockResolvedValue([]),
|
||||||
post: postMock,
|
post: postMock,
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,32 +1,532 @@
|
|||||||
import angular from 'angular';
|
import { BackendSrv, getBackendSrv } from '../services/backend_srv';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { Emitter } from '../utils/emitter';
|
||||||
import { ContextSrv } from '../services/context_srv';
|
import { ContextSrv, User } from '../services/context_srv';
|
||||||
jest.mock('app/core/store');
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { AppEvents } from '@grafana/data';
|
||||||
|
import { CoreEvents } from '../../types';
|
||||||
|
import { delay } from 'rxjs/operators';
|
||||||
|
|
||||||
describe('backend_srv', () => {
|
const getTestContext = (overides?: object) => {
|
||||||
const _httpBackend = (options: any) => {
|
const defaults = {
|
||||||
if (options.url === 'gateway-error') {
|
data: { test: 'hello world' },
|
||||||
return Promise.reject({ status: 502 });
|
ok: true,
|
||||||
}
|
status: 200,
|
||||||
return Promise.resolve({});
|
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(
|
const expectRequestCallChain = (options: any) => {
|
||||||
_httpBackend,
|
expect(parseRequestOptionsMock).toHaveBeenCalledTimes(1);
|
||||||
{} as angular.IQService,
|
expect(parseRequestOptionsMock).toHaveBeenCalledWith(options, 1337);
|
||||||
{} as angular.ITimeoutService,
|
expectCallChain(options);
|
||||||
{} as ContextSrv
|
};
|
||||||
);
|
|
||||||
|
|
||||||
describe('when handling errors', () => {
|
const expectDataSourceRequestCallChain = (options: any) => {
|
||||||
it('should return the http status code', async () => {
|
expect(parseDataSourceRequestOptionsMock).toHaveBeenCalledTimes(1);
|
||||||
try {
|
expect(parseDataSourceRequestOptionsMock).toHaveBeenCalledWith(options, 1337, undefined);
|
||||||
await _backendSrv.datasourceRequest({
|
expectCallChain(options);
|
||||||
url: 'gateway-error',
|
};
|
||||||
});
|
|
||||||
} catch (err) {
|
return {
|
||||||
expect(err.status).toBe(502);
|
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,
|
FoldersAndDashboardUids,
|
||||||
} from 'app/core/components/manage_dashboards/manage_dashboards';
|
} from 'app/core/components/manage_dashboards/manage_dashboards';
|
||||||
import { SearchSrv } from 'app/core/services/search_srv';
|
import { SearchSrv } from 'app/core/services/search_srv';
|
||||||
import { BackendSrv } from '../services/backend_srv';
|
|
||||||
import { ContextSrv } from '../services/context_srv';
|
import { ContextSrv } from '../services/context_srv';
|
||||||
|
|
||||||
const mockSection = (overides?: object): Section => {
|
const mockSection = (overides?: object): Section => {
|
||||||
@@ -593,7 +592,6 @@ function createCtrlWithStubs(searchResponse: any, tags?: any) {
|
|||||||
|
|
||||||
return new ManageDashboardsCtrl(
|
return new ManageDashboardsCtrl(
|
||||||
{ $digest: jest.fn() } as any,
|
{ $digest: jest.fn() } as any,
|
||||||
{} as BackendSrv,
|
|
||||||
searchSrvStub as SearchSrv,
|
searchSrvStub as SearchSrv,
|
||||||
{ isEditor: true } as ContextSrv
|
{ isEditor: true } as ContextSrv
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { ILocationService, IScope } from 'angular';
|
||||||
|
|
||||||
import { SearchResultsCtrl } from '../components/search/search_results';
|
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 appEvents from 'app/core/app_events';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
|
||||||
@@ -11,13 +13,15 @@ jest.mock('app/core/app_events', () => {
|
|||||||
|
|
||||||
describe('SearchResultsCtrl', () => {
|
describe('SearchResultsCtrl', () => {
|
||||||
let ctrl: any;
|
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', () => {
|
describe('when checking an item that is not checked', () => {
|
||||||
const item = { checked: false };
|
const item = { checked: false };
|
||||||
let selectionChanged = false;
|
let selectionChanged = false;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctrl = new SearchResultsCtrl({});
|
ctrl = new SearchResultsCtrl($location, $scope);
|
||||||
ctrl.onSelectionChanged = () => (selectionChanged = true);
|
ctrl.onSelectionChanged = () => (selectionChanged = true);
|
||||||
ctrl.toggleSelection(item);
|
ctrl.toggleSelection(item);
|
||||||
});
|
});
|
||||||
@@ -36,7 +40,7 @@ describe('SearchResultsCtrl', () => {
|
|||||||
let selectionChanged = false;
|
let selectionChanged = false;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctrl = new SearchResultsCtrl({});
|
ctrl = new SearchResultsCtrl($location, $scope);
|
||||||
ctrl.onSelectionChanged = () => (selectionChanged = true);
|
ctrl.onSelectionChanged = () => (selectionChanged = true);
|
||||||
ctrl.toggleSelection(item);
|
ctrl.toggleSelection(item);
|
||||||
});
|
});
|
||||||
@@ -54,7 +58,7 @@ describe('SearchResultsCtrl', () => {
|
|||||||
let selectedTag: any = null;
|
let selectedTag: any = null;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctrl = new SearchResultsCtrl({});
|
ctrl = new SearchResultsCtrl($location, $scope);
|
||||||
ctrl.onTagSelected = (tag: any) => (selectedTag = tag);
|
ctrl.onTagSelected = (tag: any) => (selectedTag = tag);
|
||||||
ctrl.selectTag('tag-test');
|
ctrl.selectTag('tag-test');
|
||||||
});
|
});
|
||||||
@@ -68,7 +72,7 @@ describe('SearchResultsCtrl', () => {
|
|||||||
let folderExpanded = false;
|
let folderExpanded = false;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctrl = new SearchResultsCtrl({});
|
ctrl = new SearchResultsCtrl($location, $scope);
|
||||||
ctrl.onFolderExpanding = () => {
|
ctrl.onFolderExpanding = () => {
|
||||||
folderExpanded = true;
|
folderExpanded = true;
|
||||||
};
|
};
|
||||||
@@ -90,7 +94,7 @@ describe('SearchResultsCtrl', () => {
|
|||||||
let folderExpanded = false;
|
let folderExpanded = false;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctrl = new SearchResultsCtrl({});
|
ctrl = new SearchResultsCtrl($location, $scope);
|
||||||
ctrl.onFolderExpanding = () => {
|
ctrl.onFolderExpanding = () => {
|
||||||
folderExpanded = true;
|
folderExpanded = true;
|
||||||
};
|
};
|
||||||
@@ -110,12 +114,12 @@ describe('SearchResultsCtrl', () => {
|
|||||||
|
|
||||||
describe('when clicking on a link in search result', () => {
|
describe('when clicking on a link in search result', () => {
|
||||||
const dashPath = 'dashboard/path';
|
const dashPath = 'dashboard/path';
|
||||||
const $location = { path: () => dashPath };
|
const $location = ({ path: () => dashPath } as any) as ILocationService;
|
||||||
const appEventsMock = appEvents as any;
|
const appEventsMock = appEvents as any;
|
||||||
|
|
||||||
describe('with the same url as current path', () => {
|
describe('with the same url as current path', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctrl = new SearchResultsCtrl($location);
|
ctrl = new SearchResultsCtrl($location, $scope);
|
||||||
const item = { url: dashPath };
|
const item = { url: dashPath };
|
||||||
ctrl.onItemClick(item);
|
ctrl.onItemClick(item);
|
||||||
});
|
});
|
||||||
@@ -128,7 +132,7 @@ describe('SearchResultsCtrl', () => {
|
|||||||
|
|
||||||
describe('with a different url than current path', () => {
|
describe('with a different url than current path', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctrl = new SearchResultsCtrl($location);
|
ctrl = new SearchResultsCtrl($location, $scope);
|
||||||
const item = { url: 'another/path' };
|
const item = { url: 'another/path' };
|
||||||
ctrl.onItemClick(item);
|
ctrl.onItemClick(item);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { SearchSrv } from 'app/core/services/search_srv';
|
import { SearchSrv } from 'app/core/services/search_srv';
|
||||||
import { BackendSrvMock } from 'test/mocks/backend_srv';
|
|
||||||
import impressionSrv from 'app/core/services/impression_srv';
|
import impressionSrv from 'app/core/services/impression_srv';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { beforeEach } from 'test/lib/common';
|
import { beforeEach } from 'test/lib/common';
|
||||||
import { BackendSrv } from '../services/backend_srv';
|
import { backendSrv } from '../services/backend_srv';
|
||||||
|
|
||||||
jest.mock('app/core/store', () => {
|
jest.mock('app/core/store', () => {
|
||||||
return {
|
return {
|
||||||
@@ -19,29 +18,32 @@ jest.mock('app/core/services/impression_srv', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('SearchSrv', () => {
|
describe('SearchSrv', () => {
|
||||||
let searchSrv: SearchSrv, backendSrvMock: BackendSrvMock;
|
let searchSrv: SearchSrv;
|
||||||
|
const searchMock = jest.spyOn(backendSrv, 'search'); // will use the mock in __mocks__
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock = new BackendSrvMock();
|
searchSrv = new SearchSrv();
|
||||||
searchSrv = new SearchSrv(backendSrvMock as BackendSrv);
|
|
||||||
|
|
||||||
contextSrv.isSignedIn = true;
|
contextSrv.isSignedIn = true;
|
||||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]);
|
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]);
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('With recent dashboards', () => {
|
describe('With recent dashboards', () => {
|
||||||
let results: any;
|
let results: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest
|
searchMock.mockImplementation(
|
||||||
.fn()
|
jest
|
||||||
.mockReturnValueOnce(
|
.fn()
|
||||||
Promise.resolve([
|
.mockReturnValueOnce(
|
||||||
{ id: 2, title: 'second but first' },
|
Promise.resolve([
|
||||||
{ id: 1, title: 'first but second' },
|
{ id: 2, title: 'second but first' },
|
||||||
])
|
{ id: 1, title: 'first but second' },
|
||||||
)
|
])
|
||||||
.mockReturnValue(Promise.resolve([]));
|
)
|
||||||
|
.mockReturnValue(Promise.resolve([]))
|
||||||
|
);
|
||||||
|
|
||||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
|
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
|
||||||
|
|
||||||
@@ -63,15 +65,17 @@ describe('SearchSrv', () => {
|
|||||||
let results: any;
|
let results: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest
|
searchMock.mockImplementation(
|
||||||
.fn()
|
jest
|
||||||
.mockReturnValueOnce(
|
.fn()
|
||||||
Promise.resolve([
|
.mockReturnValueOnce(
|
||||||
{ id: 2, title: 'two' },
|
Promise.resolve([
|
||||||
{ id: 1, title: 'one' },
|
{ id: 2, title: 'two' },
|
||||||
])
|
{ id: 1, title: 'one' },
|
||||||
)
|
])
|
||||||
.mockReturnValue(Promise.resolve([]));
|
)
|
||||||
|
.mockReturnValue(Promise.resolve([]))
|
||||||
|
);
|
||||||
|
|
||||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([4, 5, 1, 2, 3]);
|
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([4, 5, 1, 2, 3]);
|
||||||
|
|
||||||
@@ -92,7 +96,7 @@ describe('SearchSrv', () => {
|
|||||||
let results: any;
|
let results: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
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 => {
|
return searchSrv.search({ query: '' }).then(res => {
|
||||||
results = res;
|
results = res;
|
||||||
@@ -109,15 +113,17 @@ describe('SearchSrv', () => {
|
|||||||
let results: any;
|
let results: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest
|
searchMock.mockImplementation(
|
||||||
.fn()
|
jest
|
||||||
.mockReturnValueOnce(
|
.fn()
|
||||||
Promise.resolve([
|
.mockReturnValueOnce(
|
||||||
{ id: 1, title: 'starred and recent', isStarred: true },
|
Promise.resolve([
|
||||||
{ id: 2, title: 'recent' },
|
{ id: 1, title: 'starred and recent', isStarred: true },
|
||||||
])
|
{ id: 2, title: 'recent' },
|
||||||
)
|
])
|
||||||
.mockReturnValue(Promise.resolve([{ id: 1, title: 'starred and recent' }]));
|
)
|
||||||
|
.mockReturnValue(Promise.resolve([{ id: 1, title: 'starred and recent' }]))
|
||||||
|
);
|
||||||
|
|
||||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
|
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
|
||||||
return searchSrv.search({ query: '' }).then(res => {
|
return searchSrv.search({ query: '' }).then(res => {
|
||||||
@@ -140,35 +146,37 @@ describe('SearchSrv', () => {
|
|||||||
let results: any;
|
let results: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest
|
searchMock.mockImplementation(
|
||||||
.fn()
|
jest
|
||||||
.mockReturnValueOnce(Promise.resolve([]))
|
.fn()
|
||||||
.mockReturnValue(
|
.mockReturnValueOnce(Promise.resolve([]))
|
||||||
Promise.resolve([
|
.mockReturnValue(
|
||||||
{
|
Promise.resolve([
|
||||||
title: 'folder1',
|
{
|
||||||
type: 'dash-folder',
|
title: 'folder1',
|
||||||
id: 1,
|
type: 'dash-folder',
|
||||||
},
|
id: 1,
|
||||||
{
|
},
|
||||||
title: 'dash with no folder',
|
{
|
||||||
type: 'dash-db',
|
title: 'dash with no folder',
|
||||||
id: 2,
|
type: 'dash-db',
|
||||||
},
|
id: 2,
|
||||||
{
|
},
|
||||||
title: 'dash in folder1 1',
|
{
|
||||||
type: 'dash-db',
|
title: 'dash in folder1 1',
|
||||||
id: 3,
|
type: 'dash-db',
|
||||||
folderId: 1,
|
id: 3,
|
||||||
},
|
folderId: 1,
|
||||||
{
|
},
|
||||||
title: 'dash in folder1 2',
|
{
|
||||||
type: 'dash-db',
|
title: 'dash in folder1 2',
|
||||||
id: 4,
|
type: 'dash-db',
|
||||||
folderId: 1,
|
id: 4,
|
||||||
},
|
folderId: 1,
|
||||||
])
|
},
|
||||||
);
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return searchSrv.search({ query: '' }).then(res => {
|
return searchSrv.search({ query: '' }).then(res => {
|
||||||
results = res;
|
results = res;
|
||||||
@@ -188,23 +196,25 @@ describe('SearchSrv', () => {
|
|||||||
let results: any;
|
let results: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest.fn().mockReturnValue(
|
searchMock.mockImplementation(
|
||||||
Promise.resolve([
|
jest.fn().mockReturnValue(
|
||||||
{
|
Promise.resolve([
|
||||||
id: 2,
|
{
|
||||||
title: 'dash with no folder',
|
id: 2,
|
||||||
type: 'dash-db',
|
title: 'dash with no folder',
|
||||||
},
|
type: 'dash-db',
|
||||||
{
|
},
|
||||||
id: 3,
|
{
|
||||||
title: 'dash in folder1 1',
|
id: 3,
|
||||||
type: 'dash-db',
|
title: 'dash in folder1 1',
|
||||||
folderId: 1,
|
type: 'dash-db',
|
||||||
folderUid: 'uid',
|
folderId: 1,
|
||||||
folderTitle: 'folder1',
|
folderUid: 'uid',
|
||||||
folderUrl: '/dashboards/f/uid/folder1',
|
folderTitle: 'folder1',
|
||||||
},
|
folderUrl: '/dashboards/f/uid/folder1',
|
||||||
])
|
},
|
||||||
|
])
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return searchSrv.search({ query: 'search' }).then(res => {
|
return searchSrv.search({ query: 'search' }).then(res => {
|
||||||
@@ -213,7 +223,7 @@ describe('SearchSrv', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not specify folder ids', () => {
|
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', () => {
|
it('should group results by folder', () => {
|
||||||
@@ -228,27 +238,25 @@ describe('SearchSrv', () => {
|
|||||||
|
|
||||||
describe('with tags', () => {
|
describe('with tags', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest.fn();
|
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([])));
|
||||||
backendSrvMock.search.mockReturnValue(Promise.resolve([]));
|
|
||||||
|
|
||||||
return searchSrv.search({ tag: ['atag'] }).then(() => {});
|
return searchSrv.search({ tag: ['atag'] }).then(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send tags query to backend search', () => {
|
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', () => {
|
describe('with starred', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest.fn();
|
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([])));
|
||||||
backendSrvMock.search.mockReturnValue(Promise.resolve([]));
|
|
||||||
|
|
||||||
return searchSrv.search({ starred: true }).then(() => {});
|
return searchSrv.search({ starred: true }).then(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send starred query to backend search', () => {
|
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;
|
let getRecentDashboardsCalled = false;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest.fn();
|
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([])));
|
||||||
backendSrvMock.search.mockReturnValue(Promise.resolve([]));
|
|
||||||
|
|
||||||
searchSrv['getRecentDashboards'] = () => {
|
searchSrv['getRecentDashboards'] = () => {
|
||||||
getRecentDashboardsCalled = true;
|
getRecentDashboardsCalled = true;
|
||||||
@@ -276,8 +283,7 @@ describe('SearchSrv', () => {
|
|||||||
let getStarredCalled = false;
|
let getStarredCalled = false;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.search = jest.fn();
|
searchMock.mockImplementation(jest.fn().mockReturnValue(Promise.resolve([])));
|
||||||
backendSrvMock.search.mockReturnValue(Promise.resolve([]));
|
|
||||||
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]);
|
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]);
|
||||||
|
|
||||||
searchSrv['getStarred'] = () => {
|
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 { NavModelSrv } from 'app/core/core';
|
||||||
|
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||||
|
|
||||||
export default class AdminEditOrgCtrl {
|
export default class AdminEditOrgCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope: any, $routeParams: any, backendSrv: BackendSrv, $location: any, navModelSrv: NavModelSrv) {
|
constructor($scope: any, $routeParams: any, $location: any, navModelSrv: NavModelSrv) {
|
||||||
$scope.init = () => {
|
$scope.init = () => {
|
||||||
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
|
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
|
||||||
|
|
||||||
if ($routeParams.id) {
|
if ($routeParams.id) {
|
||||||
$scope.getOrg($routeParams.id);
|
promiseToDigest($scope)(Promise.all([$scope.getOrg($routeParams.id), $scope.getOrgUsers($routeParams.id)]));
|
||||||
$scope.getOrgUsers($routeParams.id);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getOrg = (id: number) => {
|
$scope.getOrg = (id: number) => {
|
||||||
backendSrv.get('/api/orgs/' + id).then((org: any) => {
|
return getBackendSrv()
|
||||||
$scope.org = org;
|
.get('/api/orgs/' + id)
|
||||||
});
|
.then((org: any) => {
|
||||||
|
$scope.org = org;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getOrgUsers = (id: number) => {
|
$scope.getOrgUsers = (id: number) => {
|
||||||
backendSrv.get('/api/orgs/' + id + '/users').then((orgUsers: any) => {
|
return getBackendSrv()
|
||||||
$scope.orgUsers = orgUsers;
|
.get('/api/orgs/' + id + '/users')
|
||||||
});
|
.then((orgUsers: any) => {
|
||||||
|
$scope.orgUsers = orgUsers;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.update = () => {
|
$scope.update = () => {
|
||||||
@@ -30,19 +34,25 @@ export default class AdminEditOrgCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backendSrv.put('/api/orgs/' + $scope.org.id, $scope.org).then(() => {
|
promiseToDigest($scope)(
|
||||||
$location.path('/admin/orgs');
|
getBackendSrv()
|
||||||
});
|
.put('/api/orgs/' + $scope.org.id, $scope.org)
|
||||||
|
.then(() => {
|
||||||
|
$location.path('/admin/orgs');
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.updateOrgUser = (orgUser: any) => {
|
$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) => {
|
$scope.removeOrgUser = (orgUser: any) => {
|
||||||
backendSrv.delete('/api/orgs/' + orgUser.orgId + '/users/' + orgUser.userId).then(() => {
|
promiseToDigest($scope)(
|
||||||
$scope.getOrgUsers($scope.org.id);
|
getBackendSrv()
|
||||||
});
|
.delete('/api/orgs/' + orgUser.orgId + '/users/' + orgUser.userId)
|
||||||
|
.then(() => $scope.getOrgUsers($scope.org.id))
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.init();
|
$scope.init();
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { NavModelSrv } from 'app/core/core';
|
import { NavModelSrv } from 'app/core/core';
|
||||||
import { User } from 'app/core/services/context_srv';
|
import { User } from 'app/core/services/context_srv';
|
||||||
import { UserSession, Scope, CoreEvents, AppEventEmitter } from 'app/types';
|
import { UserSession, Scope, CoreEvents, AppEventEmitter } from 'app/types';
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
|
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||||
|
|
||||||
export default class AdminEditUserCtrl {
|
export default class AdminEditUserCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor($scope: Scope & AppEventEmitter, $routeParams: any, $location: any, navModelSrv: NavModelSrv) {
|
||||||
$scope: Scope & AppEventEmitter,
|
|
||||||
$routeParams: any,
|
|
||||||
backendSrv: BackendSrv,
|
|
||||||
$location: any,
|
|
||||||
navModelSrv: NavModelSrv
|
|
||||||
) {
|
|
||||||
$scope.user = {};
|
$scope.user = {};
|
||||||
$scope.sessions = [];
|
$scope.sessions = [];
|
||||||
$scope.newOrg = { name: '', role: 'Editor' };
|
$scope.newOrg = { name: '', role: 'Editor' };
|
||||||
@@ -22,60 +17,74 @@ export default class AdminEditUserCtrl {
|
|||||||
|
|
||||||
$scope.init = () => {
|
$scope.init = () => {
|
||||||
if ($routeParams.id) {
|
if ($routeParams.id) {
|
||||||
$scope.getUser($routeParams.id);
|
promiseToDigest($scope)(
|
||||||
$scope.getUserSessions($routeParams.id);
|
Promise.all([
|
||||||
$scope.getUserOrgs($routeParams.id);
|
$scope.getUser($routeParams.id),
|
||||||
|
$scope.getUserSessions($routeParams.id),
|
||||||
|
$scope.getUserOrgs($routeParams.id),
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getUser = (id: number) => {
|
$scope.getUser = (id: number) => {
|
||||||
backendSrv.get('/api/users/' + id).then((user: User) => {
|
return getBackendSrv()
|
||||||
$scope.user = user;
|
.get('/api/users/' + id)
|
||||||
$scope.user_id = id;
|
.then((user: User) => {
|
||||||
$scope.permissions.isGrafanaAdmin = user.isGrafanaAdmin;
|
$scope.user = user;
|
||||||
});
|
$scope.user_id = id;
|
||||||
|
$scope.permissions.isGrafanaAdmin = user.isGrafanaAdmin;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getUserSessions = (id: number) => {
|
$scope.getUserSessions = (id: number) => {
|
||||||
backendSrv.get('/api/admin/users/' + id + '/auth-tokens').then((sessions: UserSession[]) => {
|
return getBackendSrv()
|
||||||
sessions.reverse();
|
.get('/api/admin/users/' + id + '/auth-tokens')
|
||||||
|
.then((sessions: UserSession[]) => {
|
||||||
|
sessions.reverse();
|
||||||
|
|
||||||
$scope.sessions = sessions.map((session: UserSession) => {
|
$scope.sessions = sessions.map((session: UserSession) => {
|
||||||
return {
|
return {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
isActive: session.isActive,
|
isActive: session.isActive,
|
||||||
seenAt: dateTime(session.seenAt).fromNow(),
|
seenAt: dateTime(session.seenAt).fromNow(),
|
||||||
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
||||||
clientIp: session.clientIp,
|
clientIp: session.clientIp,
|
||||||
browser: session.browser,
|
browser: session.browser,
|
||||||
browserVersion: session.browserVersion,
|
browserVersion: session.browserVersion,
|
||||||
os: session.os,
|
os: session.os,
|
||||||
osVersion: session.osVersion,
|
osVersion: session.osVersion,
|
||||||
device: session.device,
|
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.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) => {
|
$scope.revokeAllUserSessions = (tokenId: number) => {
|
||||||
backendSrv.post('/api/admin/users/' + $scope.user_id + '/logout').then(() => {
|
promiseToDigest($scope)(
|
||||||
$scope.sessions = [];
|
getBackendSrv()
|
||||||
});
|
.post('/api/admin/users/' + $scope.user_id + '/logout')
|
||||||
|
.then(() => {
|
||||||
|
$scope.sessions = [];
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.setPassword = () => {
|
$scope.setPassword = () => {
|
||||||
@@ -84,15 +93,19 @@ export default class AdminEditUserCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const payload = { password: $scope.password };
|
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 = () => {
|
$scope.updatePermissions = () => {
|
||||||
const payload = $scope.permissions;
|
const payload = $scope.permissions;
|
||||||
|
getBackendSrv().put('/api/admin/users/' + $scope.user_id + '/permissions', payload);
|
||||||
backendSrv.put('/api/admin/users/' + $scope.user_id + '/permissions', payload);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.create = () => {
|
$scope.create = () => {
|
||||||
@@ -100,15 +113,21 @@ export default class AdminEditUserCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backendSrv.post('/api/admin/users', $scope.user).then(() => {
|
promiseToDigest($scope)(
|
||||||
$location.path('/admin/users');
|
getBackendSrv()
|
||||||
});
|
.post('/api/admin/users', $scope.user)
|
||||||
|
.then(() => {
|
||||||
|
$location.path('/admin/users');
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getUserOrgs = (id: number) => {
|
$scope.getUserOrgs = (id: number) => {
|
||||||
backendSrv.get('/api/users/' + id + '/orgs').then((orgs: any) => {
|
return getBackendSrv()
|
||||||
$scope.orgs = orgs;
|
.get('/api/users/' + id + '/orgs')
|
||||||
});
|
.then((orgs: any) => {
|
||||||
|
$scope.orgs = orgs;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.update = () => {
|
$scope.update = () => {
|
||||||
@@ -116,20 +135,27 @@ export default class AdminEditUserCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backendSrv.put('/api/users/' + $scope.user_id, $scope.user).then(() => {
|
promiseToDigest($scope)(
|
||||||
$location.path('/admin/users');
|
getBackendSrv()
|
||||||
});
|
.put('/api/users/' + $scope.user_id, $scope.user)
|
||||||
|
.then(() => {
|
||||||
|
$location.path('/admin/users');
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.updateOrgUser = (orgUser: { orgId: string }) => {
|
$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 }) => {
|
$scope.removeOrgUser = (orgUser: { orgId: string }) => {
|
||||||
backendSrv.delete('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id).then(() => {
|
promiseToDigest($scope)(
|
||||||
$scope.getUser($scope.user_id);
|
getBackendSrv()
|
||||||
$scope.getUserOrgs($scope.user_id);
|
.delete('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id)
|
||||||
});
|
.then(() => Promise.all([$scope.getUser($scope.user_id), $scope.getUserOrgs($scope.user_id)]))
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.orgsSearchCache = [];
|
$scope.orgsSearchCache = [];
|
||||||
@@ -140,10 +166,14 @@ export default class AdminEditUserCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backendSrv.get('/api/orgs', { query: '' }).then((result: any) => {
|
promiseToDigest($scope)(
|
||||||
$scope.orgsSearchCache = result;
|
getBackendSrv()
|
||||||
callback(_.map(result, 'name'));
|
.get('/api/orgs', { query: '' })
|
||||||
});
|
.then((result: any) => {
|
||||||
|
$scope.orgsSearchCache = result;
|
||||||
|
callback(_.map(result, 'name'));
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addOrgUser = () => {
|
$scope.addOrgUser = () => {
|
||||||
@@ -161,10 +191,11 @@ export default class AdminEditUserCtrl {
|
|||||||
|
|
||||||
$scope.newOrg.loginOrEmail = $scope.user.login;
|
$scope.newOrg.loginOrEmail = $scope.user.login;
|
||||||
|
|
||||||
backendSrv.post('/api/orgs/' + orgInfo.id + '/users/', $scope.newOrg).then(() => {
|
promiseToDigest($scope)(
|
||||||
$scope.getUser($scope.user_id);
|
getBackendSrv()
|
||||||
$scope.getUserOrgs($scope.user_id);
|
.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) => {
|
$scope.deleteUser = (user: any) => {
|
||||||
@@ -174,9 +205,13 @@ export default class AdminEditUserCtrl {
|
|||||||
icon: 'fa-trash',
|
icon: 'fa-trash',
|
||||||
yesText: 'Delete',
|
yesText: 'Delete',
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
backendSrv.delete('/api/admin/users/' + user.id).then(() => {
|
promiseToDigest($scope)(
|
||||||
$location.path('/admin/users');
|
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';
|
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();
|
$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 { NavModelSrv } from 'app/core/core';
|
||||||
import { Scope, CoreEvents, AppEventEmitter } from 'app/types';
|
import { Scope, CoreEvents, AppEventEmitter } from 'app/types';
|
||||||
|
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||||
|
|
||||||
export default class AdminListOrgsCtrl {
|
export default class AdminListOrgsCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope: Scope & AppEventEmitter, backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
constructor($scope: Scope & AppEventEmitter, navModelSrv: NavModelSrv) {
|
||||||
$scope.init = () => {
|
$scope.init = async () => {
|
||||||
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
|
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
|
||||||
$scope.getOrgs();
|
await $scope.getOrgs();
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getOrgs = () => {
|
$scope.getOrgs = async () => {
|
||||||
backendSrv.get('/api/orgs').then((orgs: any) => {
|
const orgs = await promiseToDigest($scope)(getBackendSrv().get('/api/orgs'));
|
||||||
$scope.orgs = orgs;
|
$scope.orgs = orgs;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.deleteOrg = (org: any) => {
|
$scope.deleteOrg = (org: any) => {
|
||||||
@@ -23,10 +23,9 @@ export default class AdminListOrgsCtrl {
|
|||||||
text2: 'All dashboards for this organization will be removed!',
|
text2: 'All dashboards for this organization will be removed!',
|
||||||
icon: 'fa-trash',
|
icon: 'fa-trash',
|
||||||
yesText: 'Delete',
|
yesText: 'Delete',
|
||||||
onConfirm: () => {
|
onConfirm: async () => {
|
||||||
backendSrv.delete('/api/orgs/' + org.id).then(() => {
|
await getBackendSrv().delete('/api/orgs/' + org.id);
|
||||||
$scope.getOrgs();
|
await $scope.getOrgs();
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { getTagColorsFromName } from '@grafana/ui';
|
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 { NavModelSrv } from 'app/core/core';
|
||||||
|
import { Scope } from 'app/types/angular';
|
||||||
|
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||||
|
|
||||||
export default class AdminListUsersCtrl {
|
export default class AdminListUsersCtrl {
|
||||||
users: any;
|
users: any;
|
||||||
@@ -13,29 +15,31 @@ export default class AdminListUsersCtrl {
|
|||||||
navModel: any;
|
navModel: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
constructor(private $scope: Scope, navModelSrv: NavModelSrv) {
|
||||||
this.navModel = navModelSrv.getNav('admin', 'global-users', 0);
|
this.navModel = navModelSrv.getNav('admin', 'global-users', 0);
|
||||||
this.query = '';
|
this.query = '';
|
||||||
this.getUsers();
|
this.getUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsers() {
|
getUsers() {
|
||||||
this.backendSrv
|
promiseToDigest(this.$scope)(
|
||||||
.get(`/api/users/search?perpage=${this.perPage}&page=${this.page}&query=${this.query}`)
|
getBackendSrv()
|
||||||
.then((result: any) => {
|
.get(`/api/users/search?perpage=${this.perPage}&page=${this.page}&query=${this.query}`)
|
||||||
this.users = result.users;
|
.then((result: any) => {
|
||||||
this.page = result.page;
|
this.users = result.users;
|
||||||
this.perPage = result.perPage;
|
this.page = result.page;
|
||||||
this.totalPages = Math.ceil(result.totalCount / result.perPage);
|
this.perPage = result.perPage;
|
||||||
this.showPaging = this.totalPages > 1;
|
this.totalPages = Math.ceil(result.totalCount / result.perPage);
|
||||||
this.pages = [];
|
this.showPaging = this.totalPages > 1;
|
||||||
|
this.pages = [];
|
||||||
|
|
||||||
for (let i = 1; i < this.totalPages + 1; i++) {
|
for (let i = 1; i < this.totalPages + 1; i++) {
|
||||||
this.pages.push({ page: i, current: i === this.page });
|
this.pages.push({ page: i, current: i === this.page });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addUsersAuthLabels();
|
this.addUsersAuthLabels();
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToPage(page: any) {
|
navigateToPage(page: any) {
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import { StoreState } from 'app/types';
|
|||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import Page from 'app/core/components/Page/Page';
|
import Page from 'app/core/components/Page/Page';
|
||||||
|
|
||||||
const backendSrv = getBackendSrv();
|
|
||||||
|
|
||||||
type Settings = { [key: string]: { [key: string]: string } };
|
type Settings = { [key: string]: { [key: string]: string } };
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -29,7 +27,7 @@ export class AdminSettings extends React.PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const settings: Settings = await backendSrv.get('/api/admin/settings');
|
const settings: Settings = await getBackendSrv().get('/api/admin/settings');
|
||||||
this.setState({
|
this.setState({
|
||||||
settings,
|
settings,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { QueryPart } from 'app/core/components/query_part/query_part';
|
|||||||
import alertDef from './state/alertDef';
|
import alertDef from './state/alertDef';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import appEvents from 'app/core/app_events';
|
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 { DashboardSrv } from '../dashboard/services/DashboardSrv';
|
||||||
import DatasourceSrv from '../plugins/datasource_srv';
|
import DatasourceSrv from '../plugins/datasource_srv';
|
||||||
import { DataQuery, DataSourceApi } from '@grafana/data';
|
import { DataQuery, DataSourceApi } from '@grafana/data';
|
||||||
@@ -13,6 +13,7 @@ import { PanelModel } from 'app/features/dashboard/state';
|
|||||||
import { getDefaultCondition } from './getAlertingValidationMessage';
|
import { getDefaultCondition } from './getAlertingValidationMessage';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
|
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class AlertTabCtrl {
|
export class AlertTabCtrl {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
@@ -39,7 +40,6 @@ export class AlertTabCtrl {
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private $scope: any,
|
private $scope: any,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private dashboardSrv: DashboardSrv,
|
private dashboardSrv: DashboardSrv,
|
||||||
private uiSegmentSrv: any,
|
private uiSegmentSrv: any,
|
||||||
private datasourceSrv: DatasourceSrv
|
private datasourceSrv: DatasourceSrv
|
||||||
@@ -78,25 +78,31 @@ export class AlertTabCtrl {
|
|||||||
this.alertNotifications = [];
|
this.alertNotifications = [];
|
||||||
this.alertHistory = [];
|
this.alertHistory = [];
|
||||||
|
|
||||||
return this.backendSrv.get('/api/alert-notifications/lookup').then((res: any) => {
|
return promiseToDigest(this.$scope)(
|
||||||
this.notifications = res;
|
getBackendSrv()
|
||||||
|
.get('/api/alert-notifications/lookup')
|
||||||
|
.then((res: any) => {
|
||||||
|
this.notifications = res;
|
||||||
|
|
||||||
this.initModel();
|
this.initModel();
|
||||||
this.validateModel();
|
this.validateModel();
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAlertHistory() {
|
getAlertHistory() {
|
||||||
this.backendSrv
|
promiseToDigest(this.$scope)(
|
||||||
.get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50&type=alert`)
|
getBackendSrv()
|
||||||
.then((res: any) => {
|
.get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50&type=alert`)
|
||||||
this.alertHistory = _.map(res, ah => {
|
.then((res: any) => {
|
||||||
ah.time = this.dashboardSrv.getCurrent().formatDate(ah.time, 'MMM D, YYYY HH:mm:ss');
|
this.alertHistory = _.map(res, ah => {
|
||||||
ah.stateModel = alertDef.getStateDisplayModel(ah.newState);
|
ah.time = this.dashboardSrv.getCurrent().formatDate(ah.time, 'MMM D, YYYY HH:mm:ss');
|
||||||
ah.info = alertDef.getAlertAnnotationInfo(ah);
|
ah.stateModel = alertDef.getStateDisplayModel(ah.newState);
|
||||||
return ah;
|
ah.info = alertDef.getAlertAnnotationInfo(ah);
|
||||||
});
|
return ah;
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNotificationIcon(type: string): string {
|
getNotificationIcon(type: string): string {
|
||||||
@@ -459,15 +465,17 @@ export class AlertTabCtrl {
|
|||||||
icon: 'fa-trash',
|
icon: 'fa-trash',
|
||||||
yesText: 'Yes',
|
yesText: 'Yes',
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
this.backendSrv
|
promiseToDigest(this.$scope)(
|
||||||
.post('/api/annotations/mass-delete', {
|
getBackendSrv()
|
||||||
dashboardId: this.panelCtrl.dashboard.id,
|
.post('/api/annotations/mass-delete', {
|
||||||
panelId: this.panel.id,
|
dashboardId: this.panelCtrl.dashboard.id,
|
||||||
})
|
panelId: this.panel.id,
|
||||||
.then(() => {
|
})
|
||||||
this.alertHistory = [];
|
.then(() => {
|
||||||
this.panelCtrl.refresh();
|
this.alertHistory = [];
|
||||||
});
|
this.panelCtrl.refresh();
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { appEvents, coreModule, NavModelSrv } from 'app/core/core';
|
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 { AppEvents } from '@grafana/data';
|
||||||
|
import { IScope } from 'angular';
|
||||||
|
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class AlertNotificationEditCtrl {
|
export class AlertNotificationEditCtrl {
|
||||||
theForm: any;
|
theForm: any;
|
||||||
@@ -28,8 +30,8 @@ export class AlertNotificationEditCtrl {
|
|||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
|
private $scope: IScope,
|
||||||
private $routeParams: any,
|
private $routeParams: any,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private $location: any,
|
private $location: any,
|
||||||
private $templateCache: any,
|
private $templateCache: any,
|
||||||
navModelSrv: NavModelSrv
|
navModelSrv: NavModelSrv
|
||||||
@@ -41,33 +43,37 @@ export class AlertNotificationEditCtrl {
|
|||||||
return ['1m', '5m', '10m', '15m', '30m', '1h'];
|
return ['1m', '5m', '10m', '15m', '30m', '1h'];
|
||||||
};
|
};
|
||||||
|
|
||||||
this.backendSrv
|
promiseToDigest(this.$scope)(
|
||||||
.get(`/api/alert-notifiers`)
|
getBackendSrv()
|
||||||
.then((notifiers: any) => {
|
.get(`/api/alert-notifiers`)
|
||||||
this.notifiers = notifiers;
|
.then((notifiers: any) => {
|
||||||
|
this.notifiers = notifiers;
|
||||||
|
|
||||||
// add option templates
|
// add option templates
|
||||||
for (const notifier of this.notifiers) {
|
for (const notifier of this.notifiers) {
|
||||||
this.$templateCache.put(this.getNotifierTemplateId(notifier.type), notifier.optionsTemplate);
|
this.$templateCache.put(this.getNotifierTemplateId(notifier.type), notifier.optionsTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.$routeParams.id) {
|
if (!this.$routeParams.id) {
|
||||||
this.navModel.breadcrumbs.push({ text: 'New channel' });
|
this.navModel.breadcrumbs.push({ text: 'New channel' });
|
||||||
this.navModel.node = { text: 'New channel' };
|
this.navModel.node = { text: 'New channel' };
|
||||||
return _.defaults(this.model, this.defaults);
|
return _.defaults(this.model, this.defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv.get(`/api/alert-notifications/${this.$routeParams.id}`).then((result: any) => {
|
return getBackendSrv()
|
||||||
this.navModel.breadcrumbs.push({ text: result.name });
|
.get(`/api/alert-notifications/${this.$routeParams.id}`)
|
||||||
this.navModel.node = { text: result.name };
|
.then((result: any) => {
|
||||||
result.settings = _.defaults(result.settings, this.defaults.settings);
|
this.navModel.breadcrumbs.push({ text: result.name });
|
||||||
return result;
|
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);
|
.then((model: any) => {
|
||||||
});
|
this.model = model;
|
||||||
|
this.notifierTemplateId = this.getNotifierTemplateId(this.model.type);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
@@ -76,37 +82,45 @@ export class AlertNotificationEditCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.model.id) {
|
if (this.model.id) {
|
||||||
this.backendSrv
|
promiseToDigest(this.$scope)(
|
||||||
.put(`/api/alert-notifications/${this.model.id}`, this.model)
|
getBackendSrv()
|
||||||
.then((res: any) => {
|
.put(`/api/alert-notifications/${this.model.id}`, this.model)
|
||||||
this.model = res;
|
.then((res: any) => {
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Notification updated']);
|
this.model = res;
|
||||||
})
|
appEvents.emit(AppEvents.alertSuccess, ['Notification updated']);
|
||||||
.catch((err: any) => {
|
})
|
||||||
if (err.data && err.data.error) {
|
.catch((err: any) => {
|
||||||
appEvents.emit(AppEvents.alertError, [err.data.error]);
|
if (err.data && err.data.error) {
|
||||||
}
|
appEvents.emit(AppEvents.alertError, [err.data.error]);
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.backendSrv
|
promiseToDigest(this.$scope)(
|
||||||
.post(`/api/alert-notifications`, this.model)
|
getBackendSrv()
|
||||||
.then((res: any) => {
|
.post(`/api/alert-notifications`, this.model)
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Notification created']);
|
.then((res: any) => {
|
||||||
this.$location.path('alerting/notifications');
|
appEvents.emit(AppEvents.alertSuccess, ['Notification created']);
|
||||||
})
|
this.$location.path('alerting/notifications');
|
||||||
.catch((err: any) => {
|
})
|
||||||
if (err.data && err.data.error) {
|
.catch((err: any) => {
|
||||||
appEvents.emit(AppEvents.alertError, [err.data.error]);
|
if (err.data && err.data.error) {
|
||||||
}
|
appEvents.emit(AppEvents.alertError, [err.data.error]);
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteNotification() {
|
deleteNotification() {
|
||||||
this.backendSrv.delete(`/api/alert-notifications/${this.model.id}`).then((res: any) => {
|
promiseToDigest(this.$scope)(
|
||||||
this.model = res;
|
getBackendSrv()
|
||||||
this.$location.path('alerting/notifications');
|
.delete(`/api/alert-notifications/${this.model.id}`)
|
||||||
});
|
.then((res: any) => {
|
||||||
|
this.model = res;
|
||||||
|
this.$location.path('alerting/notifications');
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNotifierTemplateId(type: string) {
|
getNotifierTemplateId(type: string) {
|
||||||
@@ -130,7 +144,7 @@ export class AlertNotificationEditCtrl {
|
|||||||
settings: this.model.settings,
|
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 { coreModule, NavModelSrv } from 'app/core/core';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class AlertNotificationsListCtrl {
|
export class AlertNotificationsListCtrl {
|
||||||
notifications: any;
|
notifications: any;
|
||||||
navModel: any;
|
navModel: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
constructor(private $scope: IScope, navModelSrv: NavModelSrv) {
|
||||||
this.loadNotifications();
|
this.loadNotifications();
|
||||||
this.navModel = navModelSrv.getNav('alerting', 'channels', 0);
|
this.navModel = navModelSrv.getNav('alerting', 'channels', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNotifications() {
|
loadNotifications() {
|
||||||
this.backendSrv.get(`/api/alert-notifications`).then((result: any) => {
|
promiseToDigest(this.$scope)(
|
||||||
this.notifications = result;
|
getBackendSrv()
|
||||||
});
|
.get(`/api/alert-notifications`)
|
||||||
|
.then((result: any) => {
|
||||||
|
this.notifications = result;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteNotification(id: number) {
|
deleteNotification(id: number) {
|
||||||
this.backendSrv.delete(`/api/alert-notifications/${id}`).then(() => {
|
promiseToDigest(this.$scope)(
|
||||||
this.notifications = this.notifications.filter((notification: any) => {
|
getBackendSrv()
|
||||||
return notification.id !== id;
|
.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 { DashboardModel } from '../dashboard/state';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
jest.mock('@grafana/runtime/src/services/backendSrv', () => ({
|
jest.mock('@grafana/runtime', () => {
|
||||||
getBackendSrv: () => ({
|
const original = jest.requireActual('@grafana/runtime');
|
||||||
post: jest.fn(),
|
|
||||||
}),
|
return {
|
||||||
}));
|
...original,
|
||||||
|
getBackendSrv: () => ({
|
||||||
|
post: jest.fn(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { LoadingPlaceholder, JSONFormatter } from '@grafana/ui';
|
|||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||||
import { DashboardModel } from '../dashboard/state/DashboardModel';
|
import { DashboardModel } from '../dashboard/state/DashboardModel';
|
||||||
import { getBackendSrv, BackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -27,12 +27,6 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
formattedJson: any;
|
formattedJson: any;
|
||||||
clipboard: any;
|
clipboard: any;
|
||||||
backendSrv: BackendSrv = null;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.backendSrv = getBackendSrv();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.testRule();
|
this.testRule();
|
||||||
@@ -43,7 +37,7 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
|||||||
const payload = { dashboard: dashboard.getSaveModelClone(), panelId };
|
const payload = { dashboard: dashboard.getSaveModelClone(), panelId };
|
||||||
|
|
||||||
this.setState({ isLoading: true });
|
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 });
|
this.setState({ isLoading: false, testRuleResponse });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Libaries
|
// Libaries
|
||||||
import angular from 'angular';
|
import flattenDeep from 'lodash/flattenDeep';
|
||||||
import _ from 'lodash';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import './editor_ctrl';
|
import './editor_ctrl';
|
||||||
@@ -11,25 +11,16 @@ import { dedupAnnotations } from './events_processing';
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { DashboardModel } from '../dashboard/state/DashboardModel';
|
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 { 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 {
|
export class AnnotationsSrv {
|
||||||
globalAnnotationsPromise: any;
|
globalAnnotationsPromise: any;
|
||||||
alertStatesPromise: any;
|
alertStatesPromise: any;
|
||||||
datasourcePromises: any;
|
datasourcePromises: any;
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
constructor(
|
|
||||||
private $rootScope: GrafanaRootScope,
|
|
||||||
private datasourceSrv: DatasourceSrv,
|
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private timeSrv: TimeSrv
|
|
||||||
) {}
|
|
||||||
|
|
||||||
init(dashboard: DashboardModel) {
|
init(dashboard: DashboardModel) {
|
||||||
// always clearPromiseCaches when loading new dashboard
|
// always clearPromiseCaches when loading new dashboard
|
||||||
this.clearPromiseCaches();
|
this.clearPromiseCaches();
|
||||||
@@ -47,10 +38,10 @@ export class AnnotationsSrv {
|
|||||||
return Promise.all([this.getGlobalAnnotations(options), this.getAlertStates(options)])
|
return Promise.all([this.getGlobalAnnotations(options), this.getAlertStates(options)])
|
||||||
.then(results => {
|
.then(results => {
|
||||||
// combine the annotations and flatten 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
|
// 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 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') {
|
if (item.panelId && item.source.type === 'dashboard') {
|
||||||
return item.panelId === options.panel.id;
|
return item.panelId === options.panel.id;
|
||||||
@@ -61,7 +52,7 @@ export class AnnotationsSrv {
|
|||||||
annotations = dedupAnnotations(annotations);
|
annotations = dedupAnnotations(annotations);
|
||||||
|
|
||||||
// look for alert state for this panel
|
// 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 {
|
return {
|
||||||
annotations: annotations,
|
annotations: annotations,
|
||||||
@@ -73,7 +64,7 @@ export class AnnotationsSrv {
|
|||||||
err.message = err.data.message;
|
err.message = err.data.message;
|
||||||
}
|
}
|
||||||
console.log('AnnotationSrv.query error', err);
|
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 [];
|
return [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -96,7 +87,7 @@ export class AnnotationsSrv {
|
|||||||
return this.alertStatesPromise;
|
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,
|
dashboardId: options.dashboard.id,
|
||||||
});
|
});
|
||||||
return this.alertStatesPromise;
|
return this.alertStatesPromise;
|
||||||
@@ -109,7 +100,7 @@ export class AnnotationsSrv {
|
|||||||
return this.globalAnnotationsPromise;
|
return this.globalAnnotationsPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const range = this.timeSrv.timeRange();
|
const range = getTimeSrv().timeRange();
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const dsPromises = [];
|
const dsPromises = [];
|
||||||
|
|
||||||
@@ -121,7 +112,7 @@ export class AnnotationsSrv {
|
|||||||
if (annotation.snapshotData) {
|
if (annotation.snapshotData) {
|
||||||
return this.translateQueryResult(annotation, annotation.snapshotData);
|
return this.translateQueryResult(annotation, annotation.snapshotData);
|
||||||
}
|
}
|
||||||
const datasourcePromise = this.datasourceSrv.get(annotation.datasource);
|
const datasourcePromise = getDataSourceSrv().get(annotation.datasource);
|
||||||
dsPromises.push(datasourcePromise);
|
dsPromises.push(datasourcePromise);
|
||||||
promises.push(
|
promises.push(
|
||||||
datasourcePromise
|
datasourcePromise
|
||||||
@@ -137,7 +128,7 @@ export class AnnotationsSrv {
|
|||||||
.then(results => {
|
.then(results => {
|
||||||
// store response in annotation object if this is a snapshot call
|
// store response in annotation object if this is a snapshot call
|
||||||
if (dashboard.snapshot) {
|
if (dashboard.snapshot) {
|
||||||
annotation.snapshotData = angular.copy(results);
|
annotation.snapshotData = cloneDeep(results);
|
||||||
}
|
}
|
||||||
// translate result
|
// translate result
|
||||||
return this.translateQueryResult(annotation, results);
|
return this.translateQueryResult(annotation, results);
|
||||||
@@ -151,26 +142,26 @@ export class AnnotationsSrv {
|
|||||||
|
|
||||||
saveAnnotationEvent(annotation: AnnotationEvent) {
|
saveAnnotationEvent(annotation: AnnotationEvent) {
|
||||||
this.globalAnnotationsPromise = null;
|
this.globalAnnotationsPromise = null;
|
||||||
return this.backendSrv.post('/api/annotations', annotation);
|
return getBackendSrv().post('/api/annotations', annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAnnotationEvent(annotation: AnnotationEvent) {
|
updateAnnotationEvent(annotation: AnnotationEvent) {
|
||||||
this.globalAnnotationsPromise = null;
|
this.globalAnnotationsPromise = null;
|
||||||
return this.backendSrv.put(`/api/annotations/${annotation.id}`, annotation);
|
return getBackendSrv().put(`/api/annotations/${annotation.id}`, annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteAnnotationEvent(annotation: AnnotationEvent) {
|
deleteAnnotationEvent(annotation: AnnotationEvent) {
|
||||||
this.globalAnnotationsPromise = null;
|
this.globalAnnotationsPromise = null;
|
||||||
const deleteUrl = `/api/annotations/${annotation.id}`;
|
const deleteUrl = `/api/annotations/${annotation.id}`;
|
||||||
|
|
||||||
return this.backendSrv.delete(deleteUrl);
|
return getBackendSrv().delete(deleteUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
translateQueryResult(annotation: any, results: any) {
|
translateQueryResult(annotation: any, results: any) {
|
||||||
// if annotation has snapshotData
|
// if annotation has snapshotData
|
||||||
// make clone and remove it
|
// make clone and remove it
|
||||||
if (annotation.snapshotData) {
|
if (annotation.snapshotData) {
|
||||||
annotation = angular.copy(annotation);
|
annotation = cloneDeep(annotation);
|
||||||
delete annotation.snapshotData;
|
delete annotation.snapshotData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { AnnotationsSrv } from '../annotations_srv';
|
import { AnnotationsSrv } from '../annotations_srv';
|
||||||
|
|
||||||
describe('AnnotationsSrv', () => {
|
describe('AnnotationsSrv', () => {
|
||||||
const $rootScope: any = {
|
const annotationsSrv = new AnnotationsSrv();
|
||||||
onAppEvent: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const annotationsSrv = new AnnotationsSrv($rootScope, null, null, null);
|
|
||||||
|
|
||||||
describe('When translating the query result', () => {
|
describe('When translating the query result', () => {
|
||||||
const annotationSource = {
|
const annotationSource = {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function loadApiKeys(includeExpired: boolean): ThunkResult<void> {
|
|||||||
export function deleteApiKey(id: number, includeExpired: boolean): ThunkResult<void> {
|
export function deleteApiKey(id: number, includeExpired: boolean): ThunkResult<void> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
getBackendSrv()
|
getBackendSrv()
|
||||||
.delete('/api/auth/keys/' + id)
|
.delete(`/api/auth/keys/${id}`)
|
||||||
.then(dispatch(loadApiKeys(includeExpired)));
|
.then(() => dispatch(loadApiKeys(includeExpired)));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import angular from 'angular';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { iconMap } from './DashLinksEditorCtrl';
|
import { iconMap } from './DashLinksEditorCtrl';
|
||||||
import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
|
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 { DashboardSrv } from '../../services/DashboardSrv';
|
||||||
import { PanelEvents } from '@grafana/data';
|
import { PanelEvents } from '@grafana/data';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||||
|
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export type DashboardLink = { tags: any; target: string; keepTime: any; includeVars: any };
|
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 {
|
export class DashLinksContainerCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor($scope: any, $rootScope: GrafanaRootScope, dashboardSrv: DashboardSrv, linkSrv: LinkSrv) {
|
||||||
$scope: any,
|
|
||||||
$rootScope: GrafanaRootScope,
|
|
||||||
backendSrv: BackendSrv,
|
|
||||||
dashboardSrv: DashboardSrv,
|
|
||||||
linkSrv: LinkSrv
|
|
||||||
) {
|
|
||||||
const currentDashId = dashboardSrv.getCurrent().id;
|
const currentDashId = dashboardSrv.getCurrent().id;
|
||||||
|
|
||||||
function buildLinks(linkDef: any) {
|
function buildLinks(linkDef: any) {
|
||||||
@@ -154,26 +149,28 @@ export class DashLinksContainerCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.searchDashboards = (link: DashboardLink, limit: any) => {
|
$scope.searchDashboards = (link: DashboardLink, limit: any) => {
|
||||||
return backendSrv.search({ tag: link.tags, limit: limit }).then(results => {
|
return promiseToDigest($scope)(
|
||||||
return _.reduce(
|
backendSrv.search({ tag: link.tags, limit: limit }).then(results => {
|
||||||
results,
|
return _.reduce(
|
||||||
(memo, dash) => {
|
results,
|
||||||
// do not add current dashboard
|
(memo, dash) => {
|
||||||
if (dash.id !== currentDashId) {
|
// do not add current dashboard
|
||||||
memo.push({
|
if (dash.id !== currentDashId) {
|
||||||
title: dash.title,
|
memo.push({
|
||||||
url: dash.url,
|
title: dash.title,
|
||||||
target: link.target === '_self' ? '' : link.target,
|
url: dash.url,
|
||||||
icon: 'fa fa-th-large',
|
target: link.target === '_self' ? '' : link.target,
|
||||||
keepTime: link.keepTime,
|
icon: 'fa fa-th-large',
|
||||||
includeVars: link.includeVars,
|
keepTime: link.keepTime,
|
||||||
});
|
includeVars: link.includeVars,
|
||||||
}
|
});
|
||||||
return memo;
|
}
|
||||||
},
|
return memo;
|
||||||
[]
|
},
|
||||||
);
|
[]
|
||||||
});
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.fillDropdown = (link: { searchHits: any }) => {
|
$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 'jquery';
|
||||||
import _ from 'lodash';
|
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 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 { DashboardSrv } from '../../services/DashboardSrv';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
import { e2e } from '@grafana/e2e';
|
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class SettingsCtrl {
|
export class SettingsCtrl {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
@@ -26,11 +28,10 @@ export class SettingsCtrl {
|
|||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private $scope: any,
|
private $scope: IScope & Record<string, any>,
|
||||||
private $route: any,
|
private $route: any,
|
||||||
private $location: ILocationService,
|
private $location: ILocationService,
|
||||||
private $rootScope: GrafanaRootScope,
|
private $rootScope: GrafanaRootScope,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private dashboardSrv: DashboardSrv
|
private dashboardSrv: DashboardSrv
|
||||||
) {
|
) {
|
||||||
// temp hack for annotations and variables editors
|
// temp hack for annotations and variables editors
|
||||||
@@ -234,10 +235,12 @@ export class SettingsCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteDashboardConfirmed() {
|
deleteDashboardConfirmed() {
|
||||||
this.backendSrv.deleteDashboard(this.dashboard.uid, false).then(() => {
|
promiseToDigest(this.$scope)(
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
|
backendSrv.deleteDashboard(this.dashboard.uid, false).then(() => {
|
||||||
this.$location.url('/');
|
appEvents.emit(AppEvents.alertSuccess, ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
|
||||||
});
|
this.$location.url('/');
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFolderChange(folder: { id: number; title: string }) {
|
onFolderChange(folder: { id: number; title: string }) {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { IScope } from 'angular';
|
||||||
|
import { AppEvents } from '@grafana/data';
|
||||||
|
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import appEvents from 'app/core/app_events';
|
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 { ValidationSrv } from 'app/features/manage-dashboards';
|
||||||
import { ContextSrv } from 'app/core/services/context_srv';
|
import { ContextSrv } from 'app/core/services/context_srv';
|
||||||
import { AppEvents } from '@grafana/data';
|
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class FolderPickerCtrl {
|
export class FolderPickerCtrl {
|
||||||
initialTitle: string;
|
initialTitle: string;
|
||||||
@@ -28,7 +31,7 @@ export class FolderPickerCtrl {
|
|||||||
dashboardId?: number;
|
dashboardId?: number;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @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;
|
this.isEditor = this.contextSrv.isEditor;
|
||||||
|
|
||||||
if (!this.labelClass) {
|
if (!this.labelClass) {
|
||||||
@@ -45,33 +48,35 @@ export class FolderPickerCtrl {
|
|||||||
permission: 'Edit',
|
permission: 'Edit',
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.backendSrv.get('api/search', params).then((result: any) => {
|
return promiseToDigest(this.$scope)(
|
||||||
if (
|
backendSrv.get('api/search', params).then((result: any) => {
|
||||||
this.isEditor &&
|
if (
|
||||||
(query === '' ||
|
this.isEditor &&
|
||||||
query.toLowerCase() === 'g' ||
|
(query === '' ||
|
||||||
query.toLowerCase() === 'ge' ||
|
query.toLowerCase() === 'g' ||
|
||||||
query.toLowerCase() === 'gen' ||
|
query.toLowerCase() === 'ge' ||
|
||||||
query.toLowerCase() === 'gene' ||
|
query.toLowerCase() === 'gen' ||
|
||||||
query.toLowerCase() === 'gener' ||
|
query.toLowerCase() === 'gene' ||
|
||||||
query.toLowerCase() === 'genera' ||
|
query.toLowerCase() === 'gener' ||
|
||||||
query.toLowerCase() === 'general')
|
query.toLowerCase() === 'genera' ||
|
||||||
) {
|
query.toLowerCase() === 'general')
|
||||||
result.unshift({ title: this.rootName, id: 0 });
|
) {
|
||||||
}
|
result.unshift({ title: this.rootName, id: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isEditor && this.enableCreateNew && query === '') {
|
if (this.isEditor && this.enableCreateNew && query === '') {
|
||||||
result.unshift({ title: '-- New Folder --', id: -1 });
|
result.unshift({ title: '-- New Folder --', id: -1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.enableReset && query === '' && this.initialTitle !== '') {
|
if (this.enableReset && query === '' && this.initialTitle !== '') {
|
||||||
result.unshift({ title: this.initialTitle, id: null });
|
result.unshift({ title: this.initialTitle, id: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.map(result, item => {
|
return _.map(result, item => {
|
||||||
return { text: item.title, value: item.id };
|
return { text: item.title, value: item.id };
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFolderChange(option: { value: number; text: string }) {
|
onFolderChange(option: { value: number; text: string }) {
|
||||||
@@ -105,13 +110,15 @@ export class FolderPickerCtrl {
|
|||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv.createFolder({ title: this.newFolderName }).then((result: { title: string; id: number }) => {
|
return promiseToDigest(this.$scope)(
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
|
backendSrv.createFolder({ title: this.newFolderName }).then((result: { title: string; id: number }) => {
|
||||||
|
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
|
||||||
|
|
||||||
this.closeCreateFolder();
|
this.closeCreateFolder();
|
||||||
this.folder = { text: result.title, value: result.id };
|
this.folder = { text: result.title, value: result.id };
|
||||||
this.onFolderChange(this.folder);
|
this.onFolderChange(this.folder);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelCreateFolder(evt: any) {
|
cancelCreateFolder(evt: any) {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export class SaveDashboardModalCtrl {
|
|||||||
this.selectors = e2e.pages.SaveDashboardModal.selectors;
|
this.selectors = e2e.pages.SaveDashboardModal.selectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save(): void | Promise<any> {
|
||||||
if (!this.saveForm.$valid) {
|
if (!this.saveForm.$valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import angular, { ILocationService } from 'angular';
|
import angular, { ILocationService, IScope } from 'angular';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
import { TimeSrv } from '../../services/TimeSrv';
|
import { TimeSrv } from '../../services/TimeSrv';
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
import { PanelModel } from '../../state/PanelModel';
|
import { PanelModel } from '../../state/PanelModel';
|
||||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||||
|
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class ShareSnapshotCtrl {
|
export class ShareSnapshotCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
$scope: any,
|
$scope: IScope & Record<string, any>,
|
||||||
$rootScope: GrafanaRootScope,
|
$rootScope: GrafanaRootScope,
|
||||||
$location: ILocationService,
|
$location: ILocationService,
|
||||||
backendSrv: BackendSrv,
|
|
||||||
$timeout: any,
|
$timeout: any,
|
||||||
timeSrv: TimeSrv
|
timeSrv: TimeSrv
|
||||||
) {
|
) {
|
||||||
@@ -38,10 +39,14 @@ export class ShareSnapshotCtrl {
|
|||||||
];
|
];
|
||||||
|
|
||||||
$scope.init = () => {
|
$scope.init = () => {
|
||||||
backendSrv.get('/api/snapshot/shared-options').then((options: { [x: string]: any }) => {
|
promiseToDigest($scope)(
|
||||||
$scope.sharingButtonText = options['externalSnapshotName'];
|
getBackendSrv()
|
||||||
$scope.externalEnabled = options['externalEnabled'];
|
.get('/api/snapshot/shared-options')
|
||||||
});
|
.then((options: { [x: string]: any }) => {
|
||||||
|
$scope.sharingButtonText = options['externalSnapshotName'];
|
||||||
|
$scope.externalEnabled = options['externalEnabled'];
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.apiUrl = '/api/snapshots';
|
$scope.apiUrl = '/api/snapshots';
|
||||||
@@ -75,16 +80,20 @@ export class ShareSnapshotCtrl {
|
|||||||
external: external,
|
external: external,
|
||||||
};
|
};
|
||||||
|
|
||||||
backendSrv.post($scope.apiUrl, cmdData).then(
|
promiseToDigest($scope)(
|
||||||
(results: { deleteUrl: any; url: any }) => {
|
getBackendSrv()
|
||||||
$scope.loading = false;
|
.post($scope.apiUrl, cmdData)
|
||||||
$scope.deleteUrl = results.deleteUrl;
|
.then(
|
||||||
$scope.snapshotUrl = results.url;
|
(results: { deleteUrl: any; url: any }) => {
|
||||||
$scope.step = 2;
|
$scope.loading = false;
|
||||||
},
|
$scope.deleteUrl = results.deleteUrl;
|
||||||
() => {
|
$scope.snapshotUrl = results.url;
|
||||||
$scope.loading = false;
|
$scope.step = 2;
|
||||||
}
|
},
|
||||||
|
() => {
|
||||||
|
$scope.loading = false;
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,9 +161,13 @@ export class ShareSnapshotCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.deleteSnapshot = () => {
|
$scope.deleteSnapshot = () => {
|
||||||
backendSrv.get($scope.deleteUrl).then(() => {
|
promiseToDigest($scope)(
|
||||||
$scope.step = 3;
|
getBackendSrv()
|
||||||
});
|
.get($scope.deleteUrl)
|
||||||
|
.then(() => {
|
||||||
|
$scope.step = 3;
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { IScope } from 'angular';
|
||||||
|
|
||||||
import { HistoryListCtrl } from './HistoryListCtrl';
|
import { HistoryListCtrl } from './HistoryListCtrl';
|
||||||
import { versions, compare, restore } from './__mocks__/history';
|
import { compare, restore, versions } from './__mocks__/history';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
|
||||||
describe('HistoryListCtrl', () => {
|
describe('HistoryListCtrl', () => {
|
||||||
@@ -12,6 +14,7 @@ describe('HistoryListCtrl', () => {
|
|||||||
|
|
||||||
let historySrv: any;
|
let historySrv: any;
|
||||||
let $rootScope: any;
|
let $rootScope: any;
|
||||||
|
const $scope: IScope = ({ $evalAsync: jest.fn() } as any) as IScope;
|
||||||
let historyListCtrl: any;
|
let historyListCtrl: any;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
historySrv = {
|
historySrv = {
|
||||||
@@ -28,7 +31,7 @@ describe('HistoryListCtrl', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
historySrv.getHistoryList = jest.fn(() => Promise.resolve({}));
|
historySrv.getHistoryList = jest.fn(() => Promise.resolve({}));
|
||||||
|
|
||||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, {});
|
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, $scope);
|
||||||
|
|
||||||
historyListCtrl.dashboard = {
|
historyListCtrl.dashboard = {
|
||||||
id: 2,
|
id: 2,
|
||||||
@@ -84,7 +87,7 @@ describe('HistoryListCtrl', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
historySrv.getHistoryList = jest.fn(() => Promise.reject(new Error('HistoryListError')));
|
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();
|
await historyListCtrl.getLog();
|
||||||
});
|
});
|
||||||
@@ -127,7 +130,7 @@ describe('HistoryListCtrl', () => {
|
|||||||
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
||||||
historySrv.calculateDiff = jest.fn(() => Promise.resolve({}));
|
historySrv.calculateDiff = jest.fn(() => Promise.resolve({}));
|
||||||
|
|
||||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, {});
|
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, $scope);
|
||||||
|
|
||||||
historyListCtrl.dashboard = {
|
historyListCtrl.dashboard = {
|
||||||
id: 2,
|
id: 2,
|
||||||
@@ -260,7 +263,7 @@ describe('HistoryListCtrl', () => {
|
|||||||
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
||||||
historySrv.restoreDashboard = jest.fn(() => Promise.resolve());
|
historySrv.restoreDashboard = jest.fn(() => Promise.resolve());
|
||||||
|
|
||||||
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, {});
|
historyListCtrl = new HistoryListCtrl({}, $rootScope, {} as any, historySrv, $scope);
|
||||||
|
|
||||||
historyListCtrl.dashboard = {
|
historyListCtrl.dashboard = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -279,7 +282,7 @@ describe('HistoryListCtrl', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
historySrv.getHistoryList = jest.fn(() => Promise.resolve(versionsResponse));
|
||||||
historySrv.restoreDashboard = jest.fn(() => Promise.resolve());
|
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')));
|
historySrv.restoreDashboard = jest.fn(() => Promise.reject(new Error('RestoreError')));
|
||||||
historyListCtrl.restoreConfirm(RESTORE_ID);
|
historyListCtrl.restoreConfirm(RESTORE_ID);
|
||||||
await historyListCtrl.getLog();
|
await historyListCtrl.getLog();
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import angular, { ILocationService } from 'angular';
|
import angular, { ILocationService, IScope } from 'angular';
|
||||||
|
|
||||||
import locationUtil from 'app/core/utils/location_util';
|
import locationUtil from 'app/core/utils/location_util';
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './HistorySrv';
|
import { CalculateDiffOptions, HistoryListOpts, HistorySrv, RevisionsModel } from './HistorySrv';
|
||||||
import { dateTime, toUtc, DateTimeInput, AppEvents } from '@grafana/data';
|
import { AppEvents, dateTime, DateTimeInput, toUtc } from '@grafana/data';
|
||||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class HistoryListCtrl {
|
export class HistoryListCtrl {
|
||||||
appending: boolean;
|
appending: boolean;
|
||||||
@@ -30,7 +31,7 @@ export class HistoryListCtrl {
|
|||||||
private $rootScope: GrafanaRootScope,
|
private $rootScope: GrafanaRootScope,
|
||||||
private $location: ILocationService,
|
private $location: ILocationService,
|
||||||
private historySrv: HistorySrv,
|
private historySrv: HistorySrv,
|
||||||
public $scope: any
|
public $scope: IScope
|
||||||
) {
|
) {
|
||||||
this.appending = false;
|
this.appending = false;
|
||||||
this.diff = 'basic';
|
this.diff = 'basic';
|
||||||
@@ -108,18 +109,20 @@ export class HistoryListCtrl {
|
|||||||
diffType: diff,
|
diffType: diff,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.historySrv
|
return promiseToDigest(this.$scope)(
|
||||||
.calculateDiff(options)
|
this.historySrv
|
||||||
.then((response: any) => {
|
.calculateDiff(options)
|
||||||
// @ts-ignore
|
.then((response: any) => {
|
||||||
this.delta[this.diff] = response;
|
// @ts-ignore
|
||||||
})
|
this.delta[this.diff] = response;
|
||||||
.catch(() => {
|
})
|
||||||
this.mode = 'list';
|
.catch(() => {
|
||||||
})
|
this.mode = 'list';
|
||||||
.finally(() => {
|
})
|
||||||
this.loading = false;
|
.finally(() => {
|
||||||
});
|
this.loading = false;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLog(append = false) {
|
getLog(append = false) {
|
||||||
@@ -130,25 +133,27 @@ export class HistoryListCtrl {
|
|||||||
start: this.start,
|
start: this.start,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.historySrv
|
return promiseToDigest(this.$scope)(
|
||||||
.getHistoryList(this.dashboard, options)
|
this.historySrv
|
||||||
.then((revisions: any) => {
|
.getHistoryList(this.dashboard, options)
|
||||||
// set formatted dates & default values
|
.then((revisions: any) => {
|
||||||
for (const rev of revisions) {
|
// set formatted dates & default values
|
||||||
rev.createdDateString = this.formatDate(rev.created);
|
for (const rev of revisions) {
|
||||||
rev.ageString = this.formatBasicDate(rev.created);
|
rev.createdDateString = this.formatDate(rev.created);
|
||||||
rev.checked = false;
|
rev.ageString = this.formatBasicDate(rev.created);
|
||||||
}
|
rev.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.revisions = append ? this.revisions.concat(revisions) : revisions;
|
this.revisions = append ? this.revisions.concat(revisions) : revisions;
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.appending = false;
|
this.appending = false;
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
isLastPage() {
|
isLastPage() {
|
||||||
@@ -183,17 +188,19 @@ export class HistoryListCtrl {
|
|||||||
|
|
||||||
restoreConfirm(version: number) {
|
restoreConfirm(version: number) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
return this.historySrv
|
return promiseToDigest(this.$scope)(
|
||||||
.restoreDashboard(this.dashboard, version)
|
this.historySrv
|
||||||
.then((response: any) => {
|
.restoreDashboard(this.dashboard, version)
|
||||||
this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace();
|
.then((response: any) => {
|
||||||
this.$route.reload();
|
this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace();
|
||||||
this.$rootScope.appEvent(AppEvents.alertSuccess, ['Dashboard restored', 'Restored from version ' + version]);
|
this.$route.reload();
|
||||||
})
|
this.$rootScope.appEvent(AppEvents.alertSuccess, ['Dashboard restored', 'Restored from version ' + version]);
|
||||||
.catch(() => {
|
})
|
||||||
this.mode = 'list';
|
.catch(() => {
|
||||||
this.loading = false;
|
this.mode = 'list';
|
||||||
});
|
this.loading = false;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,41 @@
|
|||||||
import { versions, restore } from './__mocks__/history';
|
import { versions, restore } from './__mocks__/history';
|
||||||
import { HistorySrv } from './HistorySrv';
|
import { HistorySrv } from './HistorySrv';
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
|
|
||||||
|
const getMock = jest.fn().mockResolvedValue({});
|
||||||
|
const postMock = jest.fn().mockResolvedValue({});
|
||||||
|
|
||||||
jest.mock('app/core/store');
|
jest.mock('app/core/store');
|
||||||
|
jest.mock('@grafana/runtime', () => {
|
||||||
|
const original = jest.requireActual('@grafana/runtime');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
getBackendSrv: () => ({
|
||||||
|
post: postMock,
|
||||||
|
get: getMock,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('historySrv', () => {
|
describe('historySrv', () => {
|
||||||
const versionsResponse = versions();
|
const versionsResponse = versions();
|
||||||
const restoreResponse = restore;
|
const restoreResponse = restore;
|
||||||
|
|
||||||
const backendSrv: any = {
|
let historySrv = new HistorySrv();
|
||||||
get: jest.fn(() => Promise.resolve({})),
|
|
||||||
post: jest.fn(() => Promise.resolve({})),
|
|
||||||
};
|
|
||||||
|
|
||||||
let historySrv = new HistorySrv(backendSrv);
|
|
||||||
|
|
||||||
const dash = new DashboardModel({ id: 1 });
|
const dash = new DashboardModel({ id: 1 });
|
||||||
const emptyDash = new DashboardModel({});
|
const emptyDash = new DashboardModel({});
|
||||||
const historyListOpts = { limit: 10, start: 0 };
|
const historyListOpts = { limit: 10, start: 0 };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
describe('getHistoryList', () => {
|
describe('getHistoryList', () => {
|
||||||
it('should return a versions array for the given dashboard id', () => {
|
it('should return a versions array for the given dashboard id', () => {
|
||||||
backendSrv.get = jest.fn(() => Promise.resolve(versionsResponse));
|
getMock.mockImplementation(() => Promise.resolve(versionsResponse));
|
||||||
historySrv = new HistorySrv(backendSrv);
|
historySrv = new HistorySrv();
|
||||||
|
|
||||||
return historySrv.getHistoryList(dash, historyListOpts).then((versions: any) => {
|
return historySrv.getHistoryList(dash, historyListOpts).then((versions: any) => {
|
||||||
expect(versions).toEqual(versionsResponse);
|
expect(versions).toEqual(versionsResponse);
|
||||||
@@ -44,15 +58,15 @@ describe('historySrv', () => {
|
|||||||
describe('restoreDashboard', () => {
|
describe('restoreDashboard', () => {
|
||||||
it('should return a success response given valid parameters', () => {
|
it('should return a success response given valid parameters', () => {
|
||||||
const version = 6;
|
const version = 6;
|
||||||
backendSrv.post = jest.fn(() => Promise.resolve(restoreResponse(version)));
|
postMock.mockImplementation(() => Promise.resolve(restoreResponse(version)));
|
||||||
historySrv = new HistorySrv(backendSrv);
|
historySrv = new HistorySrv();
|
||||||
return historySrv.restoreDashboard(dash, version).then((response: any) => {
|
return historySrv.restoreDashboard(dash, version).then((response: any) => {
|
||||||
expect(response).toEqual(restoreResponse(version));
|
expect(response).toEqual(restoreResponse(version));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty object when not given an id', async () => {
|
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);
|
const rsp = await historySrv.restoreDashboard(emptyDash, 6);
|
||||||
expect(rsp).toEqual({});
|
expect(rsp).toEqual({});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export interface HistoryListOpts {
|
export interface HistoryListOpts {
|
||||||
limit: number;
|
limit: number;
|
||||||
@@ -32,23 +32,20 @@ export interface DiffTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class HistorySrv {
|
export class HistorySrv {
|
||||||
/** @ngInject */
|
|
||||||
constructor(private backendSrv: BackendSrv) {}
|
|
||||||
|
|
||||||
getHistoryList(dashboard: DashboardModel, options: HistoryListOpts) {
|
getHistoryList(dashboard: DashboardModel, options: HistoryListOpts) {
|
||||||
const id = dashboard && dashboard.id ? dashboard.id : void 0;
|
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) {
|
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) {
|
restoreDashboard(dashboard: DashboardModel, version: number) {
|
||||||
const id = dashboard && dashboard.id ? dashboard.id : void 0;
|
const id = dashboard && dashboard.id ? dashboard.id : void 0;
|
||||||
const url = `api/dashboards/id/${id}/restore`;
|
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';
|
import { QueryEditorRows } from './QueryEditorRows';
|
||||||
// Services
|
// Services
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
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';
|
import config from 'app/core/config';
|
||||||
// Types
|
// Types
|
||||||
import { PanelModel } from '../state/PanelModel';
|
import { PanelModel } from '../state/PanelModel';
|
||||||
@@ -48,7 +48,7 @@ interface State {
|
|||||||
|
|
||||||
export class QueriesTab extends PureComponent<Props, State> {
|
export class QueriesTab extends PureComponent<Props, State> {
|
||||||
datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
|
datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
|
||||||
backendSrv = getBackendSrv();
|
backendSrv = backendSrv;
|
||||||
querySubscription: Unsubscribable;
|
querySubscription: Unsubscribable;
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import $ from 'jquery';
|
|||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import { dateMath, AppEvents } from '@grafana/data';
|
import { dateMath, AppEvents } from '@grafana/data';
|
||||||
import impressionSrv from 'app/core/services/impression_srv';
|
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 { DashboardSrv } from './DashboardSrv';
|
||||||
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||||
import { UrlQueryValue } from '@grafana/runtime';
|
import { UrlQueryValue } from '@grafana/runtime';
|
||||||
@@ -15,7 +15,6 @@ import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
|||||||
export class DashboardLoaderSrv {
|
export class DashboardLoaderSrv {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private dashboardSrv: DashboardSrv,
|
private dashboardSrv: DashboardSrv,
|
||||||
private datasourceSrv: DatasourceSrv,
|
private datasourceSrv: DatasourceSrv,
|
||||||
private $http: any,
|
private $http: any,
|
||||||
@@ -46,11 +45,11 @@ export class DashboardLoaderSrv {
|
|||||||
if (type === 'script') {
|
if (type === 'script') {
|
||||||
promise = this._loadScriptedDashboard(slug);
|
promise = this._loadScriptedDashboard(slug);
|
||||||
} else if (type === 'snapshot') {
|
} 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);
|
return this._dashboardLoadFailed('Snapshot not found', true);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
promise = this.backendSrv
|
promise = backendSrv
|
||||||
.getDashboardByUid(uid)
|
.getDashboardByUid(uid)
|
||||||
.then((result: any) => {
|
.then((result: any) => {
|
||||||
if (result.meta.isFolder) {
|
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 coreModule from 'app/core/core_module';
|
||||||
import { appEvents } from 'app/core/app_events';
|
import { appEvents } from 'app/core/app_events';
|
||||||
import locationUtil from 'app/core/utils/location_util';
|
import locationUtil from 'app/core/utils/location_util';
|
||||||
import { DashboardModel } from '../state/DashboardModel';
|
import { DashboardModel } from '../state/DashboardModel';
|
||||||
import { removePanel } from '../utils/panel';
|
import { removePanel } from '../utils/panel';
|
||||||
import { DashboardMeta, CoreEvents } from 'app/types';
|
import { CoreEvents, DashboardMeta } from 'app/types';
|
||||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
import { ILocationService } from 'angular';
|
import { promiseToDigest } from '../../../core/utils/promiseToDigest';
|
||||||
import { AppEvents } from '@grafana/data';
|
|
||||||
import { PanelEvents } from '@grafana/data';
|
|
||||||
|
|
||||||
interface DashboardSaveOptions {
|
interface DashboardSaveOptions {
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
@@ -21,11 +22,7 @@ export class DashboardSrv {
|
|||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(private $rootScope: GrafanaRootScope, private $location: ILocationService) {
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private $rootScope: GrafanaRootScope,
|
|
||||||
private $location: ILocationService
|
|
||||||
) {
|
|
||||||
appEvents.on(CoreEvents.saveDashboard, this.saveDashboard.bind(this), $rootScope);
|
appEvents.on(CoreEvents.saveDashboard, this.saveDashboard.bind(this), $rootScope);
|
||||||
appEvents.on(PanelEvents.panelChangeView, this.onPanelChangeView);
|
appEvents.on(PanelEvents.panelChangeView, this.onPanelChangeView);
|
||||||
appEvents.on(CoreEvents.removePanel, this.onRemovePanel);
|
appEvents.on(CoreEvents.removePanel, this.onRemovePanel);
|
||||||
@@ -167,10 +164,12 @@ export class DashboardSrv {
|
|||||||
save(clone: any, options?: DashboardSaveOptions) {
|
save(clone: any, options?: DashboardSaveOptions) {
|
||||||
options.folderId = options.folderId >= 0 ? options.folderId : this.dashboard.meta.folderId || clone.folderId;
|
options.folderId = options.folderId >= 0 ? options.folderId : this.dashboard.meta.folderId || clone.folderId;
|
||||||
|
|
||||||
return this.backendSrv
|
return promiseToDigest(this.$rootScope)(
|
||||||
.saveDashboard(clone, options)
|
backendSrv
|
||||||
.then((data: any) => this.postSave(data))
|
.saveDashboard(clone, options)
|
||||||
.catch(this.handleSaveDashboardError.bind(this, clone, { folderId: options.folderId }));
|
.then((data: any) => this.postSave(data))
|
||||||
|
.catch(this.handleSaveDashboardError.bind(this, clone, { folderId: options.folderId }))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDashboard(
|
saveDashboard(
|
||||||
@@ -228,13 +227,17 @@ export class DashboardSrv {
|
|||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
if (isStarred) {
|
if (isStarred) {
|
||||||
promise = this.backendSrv.delete('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
promise = promiseToDigest(this.$rootScope)(
|
||||||
return false;
|
backendSrv.delete('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
||||||
});
|
return false;
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
promise = this.backendSrv.post('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
promise = promiseToDigest(this.$rootScope)(
|
||||||
return true;
|
backendSrv.post('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
||||||
});
|
return true;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise.then((res: boolean) => {
|
return promise.then((res: boolean) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Services & Utils
|
// Services & Utils
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
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 { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
import { DashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
import { DashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
@@ -38,7 +38,7 @@ export interface InitDashboardArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function redirectToNewUrl(slug: string, dispatch: ThunkDispatch, currentPath: string) {
|
async function redirectToNewUrl(slug: string, dispatch: ThunkDispatch, currentPath: string) {
|
||||||
const res = await getBackendSrv().getDashboardBySlug(slug);
|
const res = await backendSrv.getDashboardBySlug(slug);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
let newUrl = res.meta.url;
|
let newUrl = res.meta.url;
|
||||||
@@ -62,7 +62,7 @@ async function fetchDashboard(
|
|||||||
switch (args.routeInfo) {
|
switch (args.routeInfo) {
|
||||||
case DashboardRouteInfo.Home: {
|
case DashboardRouteInfo.Home: {
|
||||||
// load home dash
|
// 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 user specified a custom home dashboard redirect to that
|
||||||
if (dashDTO.redirectUri) {
|
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 { flatten, map as lodashMap, isArray, isString } from 'lodash';
|
||||||
import { map, catchError, takeUntil, mapTo, share, finalize, tap } from 'rxjs/operators';
|
import { map, catchError, takeUntil, mapTo, share, finalize, tap } from 'rxjs/operators';
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
// Types
|
// Types
|
||||||
import {
|
import {
|
||||||
DataSourceApi,
|
DataSourceApi,
|
||||||
@@ -136,7 +136,7 @@ export function runRequest(datasource: DataSourceApi, request: DataQueryRequest)
|
|||||||
|
|
||||||
function cancelNetworkRequestsOnUnsubscribe(req: DataQueryRequest) {
|
function cancelNetworkRequestsOnUnsubscribe(req: DataQueryRequest) {
|
||||||
return () => {
|
return () => {
|
||||||
getBackendSrv().resolveCancelerIfExists(req.requestId);
|
backendSrv.resolveCancelerIfExists(req.requestId);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import BasicSettings from './BasicSettings';
|
|||||||
import ButtonRow from './ButtonRow';
|
import ButtonRow from './ButtonRow';
|
||||||
// Services & Utils
|
// Services & Utils
|
||||||
import appEvents from 'app/core/app_events';
|
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';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
// Actions & selectors
|
// Actions & selectors
|
||||||
import { getDataSource, getDataSourceMeta } from '../state/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' });
|
this.setState({ isTesting: true, testingMessage: 'Testing...', testingStatus: 'info' });
|
||||||
|
|
||||||
getBackendSrv().withNoBackendCache(async () => {
|
backendSrv.withNoBackendCache(async () => {
|
||||||
try {
|
try {
|
||||||
const result = await dsApi.testDatasource();
|
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 appEvents from 'app/core/app_events';
|
||||||
import locationUtil from 'app/core/utils/location_util';
|
import locationUtil from 'app/core/utils/location_util';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
import { ILocationService } from 'angular';
|
|
||||||
import { ValidationSrv } from 'app/features/manage-dashboards';
|
import { ValidationSrv } from 'app/features/manage-dashboards';
|
||||||
import { NavModelSrv } from 'app/core/nav_model_srv';
|
import { NavModelSrv } from 'app/core/nav_model_srv';
|
||||||
import { AppEvents } from '@grafana/data';
|
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export default class CreateFolderCtrl {
|
export default class CreateFolderCtrl {
|
||||||
title = '';
|
title = '';
|
||||||
@@ -15,10 +17,10 @@ export default class CreateFolderCtrl {
|
|||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private $location: ILocationService,
|
private $location: ILocationService,
|
||||||
private validationSrv: ValidationSrv,
|
private validationSrv: ValidationSrv,
|
||||||
navModelSrv: NavModelSrv
|
navModelSrv: NavModelSrv,
|
||||||
|
private $scope: IScope
|
||||||
) {
|
) {
|
||||||
this.navModel = navModelSrv.getNav('dashboards', 'manage-dashboards', 0);
|
this.navModel = navModelSrv.getNav('dashboards', 'manage-dashboards', 0);
|
||||||
}
|
}
|
||||||
@@ -28,23 +30,27 @@ export default class CreateFolderCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv.createFolder({ title: this.title }).then((result: any) => {
|
promiseToDigest(this.$scope)(
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
|
backendSrv.createFolder({ title: this.title }).then((result: any) => {
|
||||||
this.$location.url(locationUtil.stripBaseFromUrl(result.url));
|
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
|
||||||
});
|
this.$location.url(locationUtil.stripBaseFromUrl(result.url));
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
titleChanged() {
|
titleChanged() {
|
||||||
this.titleTouched = true;
|
this.titleTouched = true;
|
||||||
|
|
||||||
this.validationSrv
|
promiseToDigest(this.$scope)(
|
||||||
.validateNewFolderName(this.title)
|
this.validationSrv
|
||||||
.then(() => {
|
.validateNewFolderName(this.title)
|
||||||
this.hasValidationError = false;
|
.then(() => {
|
||||||
})
|
this.hasValidationError = false;
|
||||||
.catch(err => {
|
})
|
||||||
this.hasValidationError = true;
|
.catch(err => {
|
||||||
this.validationError = err.message;
|
this.hasValidationError = true;
|
||||||
});
|
this.validationError = err.message;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { ILocationService, IScope } from 'angular';
|
||||||
|
|
||||||
import { FolderPageLoader } from './services/FolderPageLoader';
|
import { FolderPageLoader } from './services/FolderPageLoader';
|
||||||
import locationUtil from 'app/core/utils/location_util';
|
import locationUtil from 'app/core/utils/location_util';
|
||||||
import { NavModelSrv } from 'app/core/core';
|
import { NavModelSrv } from 'app/core/core';
|
||||||
import { ILocationService } from 'angular';
|
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export default class FolderDashboardsCtrl {
|
export default class FolderDashboardsCtrl {
|
||||||
navModel: any;
|
navModel: any;
|
||||||
@@ -10,23 +12,25 @@ export default class FolderDashboardsCtrl {
|
|||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private backendSrv: any,
|
|
||||||
navModelSrv: NavModelSrv,
|
navModelSrv: NavModelSrv,
|
||||||
private $routeParams: any,
|
private $routeParams: any,
|
||||||
$location: ILocationService
|
$location: ILocationService,
|
||||||
|
private $scope: IScope
|
||||||
) {
|
) {
|
||||||
if (this.$routeParams.uid) {
|
if (this.$routeParams.uid) {
|
||||||
this.uid = $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) => {
|
promiseToDigest(this.$scope)(
|
||||||
const url = locationUtil.stripBaseFromUrl(folder.url);
|
loader.load(this, this.uid, 'manage-folder-dashboards').then((folder: any) => {
|
||||||
|
const url = locationUtil.stripBaseFromUrl(folder.url);
|
||||||
|
|
||||||
if (url !== $location.path()) {
|
if (url !== $location.path()) {
|
||||||
$location.path(url).replace();
|
$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 {
|
export class FolderPageLoader {
|
||||||
constructor(private backendSrv: BackendSrv) {}
|
|
||||||
|
|
||||||
load(ctrl: any, uid: any, activeChildId: any) {
|
load(ctrl: any, uid: any, activeChildId: any) {
|
||||||
ctrl.navModel = {
|
ctrl.navModel = {
|
||||||
main: {
|
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;
|
ctrl.folderId = folder.id;
|
||||||
const folderTitle = folder.title;
|
const folderTitle = folder.title;
|
||||||
const folderUrl = folder.url;
|
const folderUrl = folder.url;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
|
||||||
import { FolderState, ThunkResult } from 'app/types';
|
import { FolderState, ThunkResult } from 'app/types';
|
||||||
import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel } from 'app/types/acl';
|
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> {
|
export function getFolderByUid(uid: string): ThunkResult<void> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const folder = await getBackendSrv().getFolderByUid(uid);
|
const folder = await backendSrv.getFolderByUid(uid);
|
||||||
dispatch(loadFolder(folder));
|
dispatch(loadFolder(folder));
|
||||||
dispatch(updateNavIndex(buildNavModel(folder)));
|
dispatch(updateNavIndex(buildNavModel(folder)));
|
||||||
};
|
};
|
||||||
@@ -19,7 +18,7 @@ export function getFolderByUid(uid: string): ThunkResult<void> {
|
|||||||
|
|
||||||
export function saveFolder(folder: FolderState): ThunkResult<void> {
|
export function saveFolder(folder: FolderState): ThunkResult<void> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const res = await getBackendSrv().put(`/api/folders/${folder.uid}`, {
|
const res = await backendSrv.put(`/api/folders/${folder.uid}`, {
|
||||||
title: folder.title,
|
title: folder.title,
|
||||||
version: folder.version,
|
version: folder.version,
|
||||||
});
|
});
|
||||||
@@ -33,14 +32,14 @@ export function saveFolder(folder: FolderState): ThunkResult<void> {
|
|||||||
|
|
||||||
export function deleteFolder(uid: string): ThunkResult<void> {
|
export function deleteFolder(uid: string): ThunkResult<void> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
await getBackendSrv().deleteFolder(uid, true);
|
await backendSrv.deleteFolder(uid, true);
|
||||||
dispatch(updateLocation({ path: `dashboards` }));
|
dispatch(updateLocation({ path: `dashboards` }));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFolderPermissions(uid: string): ThunkResult<void> {
|
export function getFolderPermissions(uid: string): ThunkResult<void> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const permissions = await getBackendSrv().get(`/api/folders/${uid}/permissions`);
|
const permissions = await backendSrv.get(`/api/folders/${uid}/permissions`);
|
||||||
dispatch(loadFolderPermissions(permissions));
|
dispatch(loadFolderPermissions(permissions));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -74,7 +73,7 @@ export function updateFolderPermission(itemToUpdate: DashboardAcl, level: Permis
|
|||||||
itemsToUpdate.push(updated);
|
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));
|
await dispatch(getFolderPermissions(folder.uid));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -91,7 +90,7 @@ export function removeFolderPermission(itemToDelete: DashboardAcl): ThunkResult<
|
|||||||
itemsToUpdate.push(toUpdateItem(item));
|
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));
|
await dispatch(getFolderPermissions(folder.uid));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -115,7 +114,7 @@ export function addFolderPermission(newItem: NewDashboardAclItem): ThunkResult<v
|
|||||||
permission: newItem.permission,
|
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));
|
await dispatch(getFolderPermissions(folder.uid));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { DashboardImportCtrl } from './DashboardImportCtrl';
|
import { DashboardImportCtrl } from './DashboardImportCtrl';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
describe('DashboardImportCtrl', () => {
|
describe('DashboardImportCtrl', () => {
|
||||||
const ctx: any = {};
|
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 navModelSrv: any;
|
||||||
let backendSrv: any;
|
|
||||||
let validationSrv: any;
|
let validationSrv: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -13,17 +16,13 @@ describe('DashboardImportCtrl', () => {
|
|||||||
getNav: () => {},
|
getNav: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
backendSrv = {
|
|
||||||
search: jest.fn().mockReturnValue(Promise.resolve([])),
|
|
||||||
getDashboardByUid: jest.fn().mockReturnValue(Promise.resolve([])),
|
|
||||||
get: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
validationSrv = {
|
validationSrv = {
|
||||||
validateNewDashboardName: jest.fn().mockReturnValue(Promise.resolve()),
|
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', () => {
|
describe('when uploading json', () => {
|
||||||
@@ -61,16 +60,12 @@ describe('DashboardImportCtrl', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.ctrl.gnetUrl = 'http://grafana.com/dashboards/123';
|
ctx.ctrl.gnetUrl = 'http://grafana.com/dashboards/123';
|
||||||
// setup api mock
|
// setup api mock
|
||||||
backendSrv.get = jest.fn(() => {
|
getMock.mockImplementation(() => Promise.resolve({ json: {} }));
|
||||||
return Promise.resolve({
|
|
||||||
json: {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return ctx.ctrl.checkGnetDashboard();
|
return ctx.ctrl.checkGnetDashboard();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call gnet api with correct dashboard id', () => {
|
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(() => {
|
beforeEach(() => {
|
||||||
ctx.ctrl.gnetUrl = '2342';
|
ctx.ctrl.gnetUrl = '2342';
|
||||||
// setup api mock
|
// setup api mock
|
||||||
backendSrv.get = jest.fn(() => {
|
getMock.mockImplementation(() => Promise.resolve({ json: {} }));
|
||||||
return Promise.resolve({
|
|
||||||
json: {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return ctx.ctrl.checkGnetDashboard();
|
return ctx.ctrl.checkGnetDashboard();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call gnet api with correct dashboard id', () => {
|
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 _ from 'lodash';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import locationUtil from 'app/core/utils/location_util';
|
import locationUtil from 'app/core/utils/location_util';
|
||||||
import { BackendSrv } from '@grafana/runtime';
|
|
||||||
import { ValidationSrv } from './services/ValidationSrv';
|
import { ValidationSrv } from './services/ValidationSrv';
|
||||||
import { NavModelSrv } from 'app/core/core';
|
import { NavModelSrv } from 'app/core/core';
|
||||||
import { ILocationService } from 'angular';
|
import { ILocationService } from 'angular';
|
||||||
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
export class DashboardImportCtrl {
|
export class DashboardImportCtrl {
|
||||||
navModel: any;
|
navModel: any;
|
||||||
@@ -32,7 +32,6 @@ export class DashboardImportCtrl {
|
|||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private validationSrv: ValidationSrv,
|
private validationSrv: ValidationSrv,
|
||||||
navModelSrv: NavModelSrv,
|
navModelSrv: NavModelSrv,
|
||||||
private $location: ILocationService,
|
private $location: ILocationService,
|
||||||
@@ -145,7 +144,7 @@ export class DashboardImportCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.backendSrv
|
backendSrv
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
.getDashboardByUid(this.dash.uid)
|
.getDashboardByUid(this.dash.uid)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
@@ -185,7 +184,7 @@ export class DashboardImportCtrl {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.backendSrv
|
return backendSrv
|
||||||
.post('api/dashboards/import', {
|
.post('api/dashboards/import', {
|
||||||
dashboard: this.dash,
|
dashboard: this.dash,
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
@@ -224,7 +223,7 @@ export class DashboardImportCtrl {
|
|||||||
this.gnetError = 'Could not find dashboard';
|
this.gnetError = 'Could not find dashboard';
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv
|
return backendSrv
|
||||||
.get('api/gnet/dashboards/' + dashboardId)
|
.get('api/gnet/dashboards/' + dashboardId)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.gnetInfo = res;
|
this.gnetInfo = res;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { ILocationService, IScope } from 'angular';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
import { NavModelSrv } from 'app/core/core';
|
import { NavModelSrv } from 'app/core/core';
|
||||||
import { ILocationService } from 'angular';
|
|
||||||
import { BackendSrv } from '@grafana/runtime';
|
|
||||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class SnapshotListCtrl {
|
export class SnapshotListCtrl {
|
||||||
navModel: any;
|
navModel: any;
|
||||||
@@ -12,27 +14,35 @@ export class SnapshotListCtrl {
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private $rootScope: GrafanaRootScope,
|
private $rootScope: GrafanaRootScope,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
navModelSrv: NavModelSrv,
|
navModelSrv: NavModelSrv,
|
||||||
private $location: ILocationService
|
private $location: ILocationService,
|
||||||
|
private $scope: IScope
|
||||||
) {
|
) {
|
||||||
this.navModel = navModelSrv.getNav('dashboards', 'snapshots', 0);
|
this.navModel = navModelSrv.getNav('dashboards', 'snapshots', 0);
|
||||||
this.backendSrv.get('/api/dashboard/snapshots').then((result: any) => {
|
promiseToDigest(this.$scope)(
|
||||||
const baseUrl = this.$location.absUrl().replace($location.url(), '');
|
getBackendSrv()
|
||||||
this.snapshots = result.map((snapshot: any) => ({
|
.get('/api/dashboard/snapshots')
|
||||||
...snapshot,
|
.then((result: any) => {
|
||||||
url: snapshot.externalUrl || `${baseUrl}/dashboard/snapshot/${snapshot.key}`,
|
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) {
|
removeSnapshotConfirmed(snapshot: any) {
|
||||||
_.remove(this.snapshots, { key: snapshot.key });
|
_.remove(this.snapshots, { key: snapshot.key });
|
||||||
this.backendSrv.delete('/api/snapshots/' + snapshot.key).then(
|
promiseToDigest(this.$scope)(
|
||||||
() => {},
|
getBackendSrv()
|
||||||
() => {
|
.delete('/api/snapshots/' + snapshot.key)
|
||||||
this.snapshots.push(snapshot);
|
.then(
|
||||||
}
|
() => {},
|
||||||
|
() => {
|
||||||
|
this.snapshots.push(snapshot);
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import appEvents from 'app/core/app_events';
|
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';
|
import { AppEvents } from '@grafana/data';
|
||||||
|
|
||||||
export class MoveToFolderCtrl {
|
export class MoveToFolderCtrl {
|
||||||
@@ -10,15 +10,12 @@ export class MoveToFolderCtrl {
|
|||||||
afterSave: any;
|
afterSave: any;
|
||||||
isValidFolderSelection = true;
|
isValidFolderSelection = true;
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
constructor(private backendSrv: BackendSrv) {}
|
|
||||||
|
|
||||||
onFolderChange(folder: any) {
|
onFolderChange(folder: any) {
|
||||||
this.folder = folder;
|
this.folder = folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
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) {
|
if (result.successCount > 0) {
|
||||||
const header = `Dashboard${result.successCount === 1 ? '' : 's'} Moved`;
|
const header = `Dashboard${result.successCount === 1 ? '' : 's'} Moved`;
|
||||||
const msg = `${result.successCount} dashboard${result.successCount === 1 ? '' : 's'} moved to ${
|
const msg = `${result.successCount} dashboard${result.successCount === 1 ? '' : 's'} moved to ${
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
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 = {
|
const hitTypes = {
|
||||||
FOLDER: 'dash-folder',
|
FOLDER: 'dash-folder',
|
||||||
@@ -9,9 +9,6 @@ const hitTypes = {
|
|||||||
export class ValidationSrv {
|
export class ValidationSrv {
|
||||||
rootName = 'general';
|
rootName = 'general';
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
constructor(private backendSrv: BackendSrv) {}
|
|
||||||
|
|
||||||
validateNewDashboardName(folderId: any, name: string) {
|
validateNewDashboardName(folderId: any, name: string) {
|
||||||
return this.validate(folderId, name, 'A dashboard in this folder with the same name already exists');
|
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 = [];
|
const promises = [];
|
||||||
promises.push(this.backendSrv.search({ type: hitTypes.FOLDER, folderIds: [folderId], query: name }));
|
promises.push(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.DASHBOARD, folderIds: [folderId], query: name }));
|
||||||
|
|
||||||
return Promise.all(promises).then(res => {
|
return Promise.all(promises).then(res => {
|
||||||
let hits: any[] = [];
|
let hits: any[] = [];
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import config from 'app/core/config';
|
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';
|
import { NavModelSrv } from 'app/core/core';
|
||||||
|
|
||||||
export class NewOrgCtrl {
|
export class NewOrgCtrl {
|
||||||
/** @ngInject */
|
/** @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.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
|
||||||
$scope.newOrg = { name: '' };
|
$scope.newOrg = { name: '' };
|
||||||
|
|
||||||
$scope.createOrg = () => {
|
$scope.createOrg = () => {
|
||||||
backendSrv.post('/api/orgs/', $scope.newOrg).then((result: any) => {
|
getBackendSrv()
|
||||||
backendSrv.post('/api/user/using/' + result.orgId).then(() => {
|
.post('/api/orgs/', $scope.newOrg)
|
||||||
window.location.href = config.appSubUrl + '/org';
|
.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 angular from 'angular';
|
||||||
import config from 'app/core/config';
|
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 {
|
export class SelectOrgCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope: any, backendSrv: BackendSrv, contextSrv: any) {
|
constructor($scope: any, contextSrv: any) {
|
||||||
contextSrv.sidemenu = false;
|
contextSrv.sidemenu = false;
|
||||||
|
|
||||||
$scope.navModel = {
|
$scope.navModel = {
|
||||||
@@ -20,15 +21,21 @@ export class SelectOrgCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.getUserOrgs = () => {
|
$scope.getUserOrgs = () => {
|
||||||
backendSrv.get('/api/user/orgs').then((orgs: any) => {
|
promiseToDigest($scope)(
|
||||||
$scope.orgs = orgs;
|
getBackendSrv()
|
||||||
});
|
.get('/api/user/orgs')
|
||||||
|
.then((orgs: any) => {
|
||||||
|
$scope.orgs = orgs;
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.setUsingOrg = (org: any) => {
|
$scope.setUsingOrg = (org: any) => {
|
||||||
backendSrv.post('/api/user/using/' + org.orgId).then(() => {
|
getBackendSrv()
|
||||||
window.location.href = config.appSubUrl + '/';
|
.post('/api/user/using/' + org.orgId)
|
||||||
});
|
.then(() => {
|
||||||
|
window.location.href = config.appSubUrl + '/';
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.init();
|
$scope.init();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
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 { NavModelSrv } from 'app/core/core';
|
||||||
import { ILocationService } from 'angular';
|
import { ILocationService, IScope } from 'angular';
|
||||||
|
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||||
|
|
||||||
export class UserInviteCtrl {
|
export class UserInviteCtrl {
|
||||||
navModel: any;
|
navModel: any;
|
||||||
@@ -9,7 +10,7 @@ export class UserInviteCtrl {
|
|||||||
inviteForm: any;
|
inviteForm: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @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.navModel = navModelSrv.getNav('cfg', 'users', 0);
|
||||||
|
|
||||||
this.invite = {
|
this.invite = {
|
||||||
@@ -25,9 +26,13 @@ export class UserInviteCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv.post('/api/org/invites', this.invite).then(() => {
|
promiseToDigest(this.$scope)(
|
||||||
this.$location.path('org/users/');
|
getBackendSrv()
|
||||||
});
|
.post('/api/org/invites', this.invite)
|
||||||
|
.then(() => {
|
||||||
|
this.$location.path('org/users/');
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import './link_srv';
|
import './link_srv';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
function panelLinksEditor() {
|
function panelLinksEditor() {
|
||||||
return {
|
return {
|
||||||
@@ -17,7 +17,7 @@ function panelLinksEditor() {
|
|||||||
|
|
||||||
export class PanelLinksEditorCtrl {
|
export class PanelLinksEditorCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope: any, backendSrv: BackendSrv) {
|
constructor($scope: any) {
|
||||||
$scope.panel.links = $scope.panel.links || [];
|
$scope.panel.links = $scope.panel.links || [];
|
||||||
|
|
||||||
$scope.addLink = () => {
|
$scope.addLink = () => {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import coreModule from '../../core/core_module';
|
import coreModule from '../../core/core_module';
|
||||||
import { ILocationService } from 'angular';
|
import { ILocationService, IScope } from 'angular';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { NavModelSrv } from 'app/core/nav_model_srv';
|
import { NavModelSrv } from 'app/core/nav_model_srv';
|
||||||
import { AppEventEmitter } from 'app/types';
|
import { AppEventEmitter } from 'app/types';
|
||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
|
import { promiseToDigest } from '../../core/utils/promiseToDigest';
|
||||||
|
|
||||||
export interface PlaylistItem {
|
export interface PlaylistItem {
|
||||||
value: any;
|
value: any;
|
||||||
@@ -29,8 +30,7 @@ export class PlaylistEditCtrl {
|
|||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private $scope: AppEventEmitter,
|
private $scope: IScope & AppEventEmitter,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private $location: ILocationService,
|
private $location: ILocationService,
|
||||||
$route: any,
|
$route: any,
|
||||||
navModelSrv: NavModelSrv
|
navModelSrv: NavModelSrv
|
||||||
@@ -41,13 +41,21 @@ export class PlaylistEditCtrl {
|
|||||||
if ($route.current.params.id) {
|
if ($route.current.params.id) {
|
||||||
const playlistId = $route.current.params.id;
|
const playlistId = $route.current.params.id;
|
||||||
|
|
||||||
backendSrv.get('/api/playlists/' + playlistId).then((result: any) => {
|
promiseToDigest(this.$scope)(
|
||||||
this.playlist = result;
|
getBackendSrv()
|
||||||
});
|
.get('/api/playlists/' + playlistId)
|
||||||
|
.then((result: any) => {
|
||||||
|
this.playlist = result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
backendSrv.get('/api/playlists/' + playlistId + '/items').then((result: any) => {
|
promiseToDigest(this.$scope)(
|
||||||
this.playlistItems = result;
|
getBackendSrv()
|
||||||
});
|
.get('/api/playlists/' + playlistId + '/items')
|
||||||
|
.then((result: any) => {
|
||||||
|
this.playlistItems = result;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +107,8 @@ export class PlaylistEditCtrl {
|
|||||||
playlist.items = playlistItems;
|
playlist.items = playlistItems;
|
||||||
|
|
||||||
savePromise = playlist.id
|
savePromise = playlist.id
|
||||||
? this.backendSrv.put('/api/playlists/' + playlist.id, playlist)
|
? promiseToDigest(this.$scope)(getBackendSrv().put('/api/playlists/' + playlist.id, playlist))
|
||||||
: this.backendSrv.post('/api/playlists', playlist);
|
: promiseToDigest(this.$scope)(getBackendSrv().post('/api/playlists', playlist));
|
||||||
|
|
||||||
savePromise.then(
|
savePromise.then(
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import { IScope, ITimeoutService } from 'angular';
|
||||||
|
|
||||||
import coreModule from '../../core/core_module';
|
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 {
|
export class PlaylistSearchCtrl {
|
||||||
query: any;
|
query: any;
|
||||||
@@ -8,7 +11,7 @@ export class PlaylistSearchCtrl {
|
|||||||
searchStarted: any;
|
searchStarted: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($timeout: any, private backendSrv: BackendSrv) {
|
constructor(private $scope: IScope, $timeout: ITimeoutService) {
|
||||||
this.query = { query: '', tag: [], starred: false, limit: 20 };
|
this.query = { query: '', tag: [], starred: false, limit: 20 };
|
||||||
|
|
||||||
$timeout(() => {
|
$timeout(() => {
|
||||||
@@ -22,12 +25,14 @@ export class PlaylistSearchCtrl {
|
|||||||
this.tagsMode = false;
|
this.tagsMode = false;
|
||||||
const prom: any = {};
|
const prom: any = {};
|
||||||
|
|
||||||
prom.promise = this.backendSrv.search(this.query).then(result => {
|
prom.promise = promiseToDigest(this.$scope)(
|
||||||
return {
|
backendSrv.search(this.query).then(result => {
|
||||||
dashboardResult: result,
|
return {
|
||||||
tagResult: [],
|
dashboardResult: result,
|
||||||
};
|
tagResult: [],
|
||||||
});
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this.searchStarted(prom);
|
this.searchStarted(prom);
|
||||||
}
|
}
|
||||||
@@ -52,12 +57,14 @@ export class PlaylistSearchCtrl {
|
|||||||
|
|
||||||
getTags() {
|
getTags() {
|
||||||
const prom: any = {};
|
const prom: any = {};
|
||||||
prom.promise = this.backendSrv.get('/api/dashboards/tags').then((result: any) => {
|
prom.promise = promiseToDigest(this.$scope)(
|
||||||
return {
|
backendSrv.get('/api/dashboards/tags').then((result: any) => {
|
||||||
dashboardResult: [],
|
return {
|
||||||
tagResult: result,
|
dashboardResult: [],
|
||||||
} as any;
|
tagResult: result,
|
||||||
});
|
} as any;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this.searchStarted(prom);
|
this.searchStarted(prom);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import locationUtil from 'app/core/utils/location_util';
|
|||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import { store } from 'app/store/store';
|
import { store } from 'app/store/store';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export const queryParamsToPreserve: { [key: string]: boolean } = {
|
export const queryParamsToPreserve: { [key: string]: boolean } = {
|
||||||
kiosk: true,
|
kiosk: true,
|
||||||
@@ -28,7 +29,7 @@ export class PlaylistSrv {
|
|||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $location: any, private $timeout: any, private backendSrv: any) {}
|
constructor(private $location: any, private $timeout: any) {}
|
||||||
|
|
||||||
next() {
|
next() {
|
||||||
this.$timeout.cancel(this.cancelPromise);
|
this.$timeout.cancel(this.cancelPromise);
|
||||||
@@ -89,13 +90,17 @@ export class PlaylistSrv {
|
|||||||
|
|
||||||
appEvents.emit(CoreEvents.playlistStarted);
|
appEvents.emit(CoreEvents.playlistStarted);
|
||||||
|
|
||||||
return this.backendSrv.get(`/api/playlists/${playlistId}`).then((playlist: any) => {
|
return getBackendSrv()
|
||||||
return this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then((dashboards: any) => {
|
.get(`/api/playlists/${playlistId}`)
|
||||||
this.dashboards = dashboards;
|
.then((playlist: any) => {
|
||||||
this.interval = kbn.interval_to_ms(playlist.interval);
|
return getBackendSrv()
|
||||||
this.next();
|
.get(`/api/playlists/${playlistId}/dashboards`)
|
||||||
|
.then((dashboards: any) => {
|
||||||
|
this.dashboards = dashboards;
|
||||||
|
this.interval = kbn.interval_to_ms(playlist.interval);
|
||||||
|
this.next();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
|||||||
@@ -1,37 +1,47 @@
|
|||||||
|
import { IScope } from 'angular';
|
||||||
import _ from 'lodash';
|
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 { 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 {
|
export class PlaylistsCtrl {
|
||||||
playlists: any;
|
playlists: any;
|
||||||
navModel: any;
|
navModel: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $scope: any, private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
constructor(private $scope: IScope & AppEventEmitter, navModelSrv: NavModelSrv) {
|
||||||
this.navModel = navModelSrv.getNav('dashboards', 'playlists', 0);
|
this.navModel = navModelSrv.getNav('dashboards', 'playlists', 0);
|
||||||
|
promiseToDigest($scope)(
|
||||||
backendSrv.get('/api/playlists').then((result: any) => {
|
getBackendSrv()
|
||||||
this.playlists = result.map((item: any) => {
|
.get('/api/playlists')
|
||||||
item.startUrl = `playlists/play/${item.id}`;
|
.then((result: any) => {
|
||||||
return item;
|
this.playlists = result.map((item: any) => {
|
||||||
});
|
item.startUrl = `playlists/play/${item.id}`;
|
||||||
});
|
return item;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
removePlaylistConfirmed(playlist: any) {
|
removePlaylistConfirmed(playlist: any) {
|
||||||
_.remove(this.playlists, { id: playlist.id });
|
_.remove(this.playlists, { id: playlist.id });
|
||||||
|
|
||||||
this.backendSrv.delete('/api/playlists/' + playlist.id).then(
|
promiseToDigest(this.$scope)(
|
||||||
() => {
|
getBackendSrv()
|
||||||
this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist deleted']);
|
.delete('/api/playlists/' + playlist.id)
|
||||||
},
|
.then(
|
||||||
() => {
|
() => {
|
||||||
this.$scope.appEvent(AppEvents.alertError, ['Unable to delete playlist']);
|
this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist deleted']);
|
||||||
this.playlists.push(playlist);
|
},
|
||||||
}
|
() => {
|
||||||
|
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 = [
|
ctx.dashboardresult = [
|
||||||
{ id: 2, title: 'dashboard: 2' },
|
{ id: 2, title: 'dashboard: 2' },
|
||||||
|
|||||||
@@ -3,6 +3,18 @@ import configureMockStore from 'redux-mock-store';
|
|||||||
import { PlaylistSrv } from '../playlist_srv';
|
import { PlaylistSrv } from '../playlist_srv';
|
||||||
import { setStore } from 'app/store/store';
|
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>();
|
const mockStore = configureMockStore<any, any>();
|
||||||
|
|
||||||
setStore(
|
setStore(
|
||||||
@@ -14,19 +26,6 @@ setStore(
|
|||||||
const dashboards = [{ url: 'dash1' }, { url: 'dash2' }];
|
const dashboards = [{ url: 'dash1' }, { url: 'dash2' }];
|
||||||
|
|
||||||
const createPlaylistSrv = (): [PlaylistSrv, { url: jest.MockInstance<any, any> }] => {
|
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 = {
|
const mockLocation = {
|
||||||
url: jest.fn(),
|
url: jest.fn(),
|
||||||
search: () => ({}),
|
search: () => ({}),
|
||||||
@@ -36,7 +35,7 @@ const createPlaylistSrv = (): [PlaylistSrv, { url: jest.MockInstance<any, any> }
|
|||||||
const mockTimeout = jest.fn();
|
const mockTimeout = jest.fn();
|
||||||
(mockTimeout as any).cancel = 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] => {
|
const mockWindowLocation = (): [jest.MockInstance<any, any>, () => void] => {
|
||||||
@@ -67,6 +66,20 @@ describe('PlaylistSrv', () => {
|
|||||||
const initialUrl = 'http://localhost/playlist';
|
const initialUrl = 'http://localhost/playlist';
|
||||||
|
|
||||||
beforeEach(() => {
|
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();
|
[srv] = createPlaylistSrv();
|
||||||
[hrefMock, unmockLocation] = mockWindowLocation();
|
[hrefMock, unmockLocation] = mockWindowLocation();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import extend from 'lodash/extend';
|
import extend from 'lodash/extend';
|
||||||
|
|
||||||
import { PluginDashboard } from 'app/types';
|
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 { appEvents } from 'app/core/core';
|
||||||
import DashboardsTable from 'app/features/datasources/DashboardsTable';
|
import DashboardsTable from 'app/features/datasources/DashboardsTable';
|
||||||
import { AppEvents, PluginMeta, DataSourceApi } from '@grafana/data';
|
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';
|
import { PluginMeta } from '@grafana/data';
|
||||||
|
|
||||||
type PluginCache = {
|
type PluginCache = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import _ from 'lodash';
|
import sortBy from 'lodash/sortBy';
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
|
|
||||||
// Services & Utils
|
// Services & Utils
|
||||||
@@ -17,7 +17,7 @@ import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
|||||||
import { expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
import { expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
||||||
|
|
||||||
export class DatasourceSrv implements DataSourceService {
|
export class DatasourceSrv implements DataSourceService {
|
||||||
datasources: Record<string, DataSourceApi>;
|
datasources: Record<string, DataSourceApi> = {};
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
@@ -38,7 +38,7 @@ export class DatasourceSrv implements DataSourceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Interpolation here is to support template variable in data source selection
|
// 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)) {
|
if (Array.isArray(value)) {
|
||||||
return value[0];
|
return value[0];
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ export class DatasourceSrv implements DataSourceService {
|
|||||||
return this.loadDatasource(name);
|
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)
|
// Expression Datasource (not a real datasource)
|
||||||
if (name === expressionDatasource.name) {
|
if (name === expressionDatasource.name) {
|
||||||
this.datasources[name] = expressionDatasource as any;
|
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 Promise.reject({ message: `Datasource named ${name} was not found` });
|
||||||
}
|
}
|
||||||
|
|
||||||
return importDataSourcePlugin(dsConfig.meta)
|
try {
|
||||||
.then(dsPlugin => {
|
const dsPlugin = await importDataSourcePlugin(dsConfig.meta);
|
||||||
// check if its in cache now
|
// check if its in cache now
|
||||||
if (this.datasources[name]) {
|
if (this.datasources[name]) {
|
||||||
return this.datasources[name];
|
return this.datasources[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is only one constructor argument it is instanceSettings
|
// If there is only one constructor argument it is instanceSettings
|
||||||
const useAngular = dsPlugin.DataSourceClass.length !== 1;
|
const useAngular = dsPlugin.DataSourceClass.length !== 1;
|
||||||
const instance: DataSourceApi = useAngular
|
const instance: DataSourceApi = useAngular
|
||||||
? this.$injector.instantiate(dsPlugin.DataSourceClass, {
|
? this.$injector.instantiate(dsPlugin.DataSourceClass, {
|
||||||
instanceSettings: dsConfig,
|
instanceSettings: dsConfig,
|
||||||
})
|
})
|
||||||
: new dsPlugin.DataSourceClass(dsConfig);
|
: new dsPlugin.DataSourceClass(dsConfig);
|
||||||
|
|
||||||
instance.components = dsPlugin.components;
|
instance.components = dsPlugin.components;
|
||||||
instance.meta = dsConfig.meta;
|
instance.meta = dsConfig.meta;
|
||||||
|
|
||||||
// store in instance cache
|
// store in instance cache
|
||||||
this.datasources[name] = instance;
|
this.datasources[name] = instance;
|
||||||
return instance;
|
return instance;
|
||||||
})
|
} catch (err) {
|
||||||
.catch(err => {
|
this.$rootScope.appEvent(AppEvents.alertError, [dsConfig.name + ' plugin failed', err.toString()]);
|
||||||
this.$rootScope.appEvent(AppEvents.alertError, [dsConfig.name + ' plugin failed', err.toString()]);
|
return Promise.reject({ message: `Datasource named ${name} was not found` });
|
||||||
return undefined;
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll() {
|
getAll() {
|
||||||
@@ -103,7 +102,7 @@ export class DatasourceSrv implements DataSourceService {
|
|||||||
|
|
||||||
getExternal() {
|
getExternal() {
|
||||||
const datasources = this.getAll().filter(ds => !ds.meta.builtIn);
|
const datasources = this.getAll().filter(ds => !ds.meta.builtIn);
|
||||||
return _.sortBy(datasources, ['name']);
|
return sortBy(datasources, ['name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAnnotationSources() {
|
getAnnotationSources() {
|
||||||
@@ -111,8 +110,8 @@ export class DatasourceSrv implements DataSourceService {
|
|||||||
|
|
||||||
this.addDataSourceVariables(sources);
|
this.addDataSourceVariables(sources);
|
||||||
|
|
||||||
_.each(config.datasources, value => {
|
Object.values(config.datasources).forEach(value => {
|
||||||
if (value.meta && value.meta.annotations) {
|
if (value.meta?.annotations) {
|
||||||
sources.push(value);
|
sources.push(value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -123,8 +122,8 @@ export class DatasourceSrv implements DataSourceService {
|
|||||||
getMetricSources(options?: { skipVariables?: boolean }) {
|
getMetricSources(options?: { skipVariables?: boolean }) {
|
||||||
const metricSources: DataSourceSelectItem[] = [];
|
const metricSources: DataSourceSelectItem[] = [];
|
||||||
|
|
||||||
_.each(config.datasources, (value, key) => {
|
Object.entries(config.datasources).forEach(([key, value]) => {
|
||||||
if (value.meta && value.meta.metrics) {
|
if (value.meta?.metrics) {
|
||||||
let metricSource = { value: key, name: key, meta: value.meta, sort: key };
|
let metricSource = { value: key, name: key, meta: value.meta, sort: key };
|
||||||
|
|
||||||
//Make sure grafana and mixed are sorted at the bottom
|
//Make sure grafana and mixed are sorted at the bottom
|
||||||
@@ -164,29 +163,22 @@ export class DatasourceSrv implements DataSourceService {
|
|||||||
|
|
||||||
addDataSourceVariables(list: any[]) {
|
addDataSourceVariables(list: any[]) {
|
||||||
// look for data source variables
|
// look for data source variables
|
||||||
for (let i = 0; i < this.templateSrv.variables.length; i++) {
|
this.templateSrv.variables
|
||||||
const variable = this.templateSrv.variables[i];
|
.filter(variable => variable.type === 'datasource')
|
||||||
if (variable.type !== 'datasource') {
|
.forEach(variable => {
|
||||||
continue;
|
const first = variable.current.value === 'default' ? config.defaultDatasource : variable.current.value;
|
||||||
}
|
const ds = config.datasources[first];
|
||||||
|
|
||||||
let first = variable.current.value;
|
if (ds) {
|
||||||
if (first === 'default') {
|
const key = `$${variable.name}`;
|
||||||
first = config.defaultDatasource;
|
list.push({
|
||||||
}
|
name: key,
|
||||||
|
value: key,
|
||||||
const ds = config.datasources[first];
|
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 { Button } from '@grafana/ui';
|
||||||
import { PluginMeta, AppPlugin, deprecationWarning } from '@grafana/data';
|
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';
|
import { css } from 'emotion';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,62 +1,70 @@
|
|||||||
import { coreModule, NavModelSrv } from 'app/core/core';
|
import { coreModule, NavModelSrv } from 'app/core/core';
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
import { UserSession } from 'app/types';
|
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 {
|
export class ProfileCtrl {
|
||||||
sessions: object[] = [];
|
sessions: object[] = [];
|
||||||
navModel: any;
|
navModel: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
constructor(private $scope: IScope, navModelSrv: NavModelSrv) {
|
||||||
this.getUserSessions();
|
this.getUserSessions();
|
||||||
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
|
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserSessions() {
|
getUserSessions() {
|
||||||
this.backendSrv.get('/api/user/auth-tokens').then((sessions: UserSession[]) => {
|
promiseToDigest(this.$scope)(
|
||||||
sessions.reverse();
|
getBackendSrv()
|
||||||
|
.get('/api/user/auth-tokens')
|
||||||
|
.then((sessions: UserSession[]) => {
|
||||||
|
sessions.reverse();
|
||||||
|
|
||||||
const found = sessions.findIndex((session: UserSession) => {
|
const found = sessions.findIndex((session: UserSession) => {
|
||||||
return session.isActive;
|
return session.isActive;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
const now = sessions[found];
|
const now = sessions[found];
|
||||||
sessions.splice(found, found);
|
sessions.splice(found, found);
|
||||||
sessions.unshift(now);
|
sessions.unshift(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sessions = sessions.map((session: UserSession) => {
|
this.sessions = sessions.map((session: UserSession) => {
|
||||||
return {
|
return {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
isActive: session.isActive,
|
isActive: session.isActive,
|
||||||
seenAt: dateTime(session.seenAt).fromNow(),
|
seenAt: dateTime(session.seenAt).fromNow(),
|
||||||
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
||||||
clientIp: session.clientIp,
|
clientIp: session.clientIp,
|
||||||
browser: session.browser,
|
browser: session.browser,
|
||||||
browserVersion: session.browserVersion,
|
browserVersion: session.browserVersion,
|
||||||
os: session.os,
|
os: session.os,
|
||||||
osVersion: session.osVersion,
|
osVersion: session.osVersion,
|
||||||
device: session.device,
|
device: session.device,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
revokeUserSession(tokenId: number) {
|
revokeUserSession(tokenId: number) {
|
||||||
this.backendSrv
|
promiseToDigest(this.$scope)(
|
||||||
.post('/api/user/revoke-auth-token', {
|
getBackendSrv()
|
||||||
authTokenId: tokenId,
|
.post('/api/user/revoke-auth-token', {
|
||||||
})
|
authTokenId: tokenId,
|
||||||
.then(() => {
|
})
|
||||||
this.sessions = this.sessions.filter((session: UserSession) => {
|
.then(() => {
|
||||||
if (session.id === tokenId) {
|
this.sessions = this.sessions.filter((session: UserSession) => {
|
||||||
return false;
|
if (session.id === tokenId) {
|
||||||
}
|
return false;
|
||||||
return true;
|
}
|
||||||
});
|
return true;
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import { ConstantVariable } from './constant_variable';
|
|||||||
import { AdhocVariable } from './adhoc_variable';
|
import { AdhocVariable } from './adhoc_variable';
|
||||||
import { TextBoxVariable } from './TextBoxVariable';
|
import { TextBoxVariable } from './TextBoxVariable';
|
||||||
|
|
||||||
coreModule.factory('templateSrv', () => {
|
coreModule.factory('templateSrv', () => templateSrv);
|
||||||
return templateSrv;
|
|
||||||
});
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
VariableSrv,
|
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' }];
|
datasource.metricFindQuery = async () => [{ value: 'test', label: 'test' }];
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataSourceInstanceSettings,
|
DataSourceInstanceSettings,
|
||||||
} from '@grafana/data';
|
} 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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
|
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
|
||||||
@@ -48,7 +48,6 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery,
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>,
|
instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv,
|
private templateSrv: TemplateSrv,
|
||||||
private timeSrv: TimeSrv
|
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)
|
return this.awsRequest('/api/tsdb/query', request)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
if (!res.results) {
|
if (!res.results) {
|
||||||
@@ -530,9 +529,11 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery,
|
|||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.backendSrv.datasourceRequest(options).then((result: any) => {
|
return getBackendSrv()
|
||||||
return result.data;
|
.datasourceRequest(options)
|
||||||
});
|
.then((result: any) => {
|
||||||
|
return result.data;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultRegion() {
|
getDefaultRegion() {
|
||||||
|
|||||||
@@ -7,10 +7,17 @@ import { CustomVariable } from 'app/features/templating/all';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { CloudWatchQuery } from '../types';
|
import { CloudWatchQuery } from '../types';
|
||||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
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';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
getBackendSrv: () => backendSrv,
|
||||||
|
}));
|
||||||
|
|
||||||
describe('CloudWatchDatasource', () => {
|
describe('CloudWatchDatasource', () => {
|
||||||
|
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||||
|
|
||||||
const instanceSettings = {
|
const instanceSettings = {
|
||||||
jsonData: { defaultRegion: 'us-east-1' },
|
jsonData: { defaultRegion: 'us-east-1' },
|
||||||
name: 'TestDatasource',
|
name: 'TestDatasource',
|
||||||
@@ -29,14 +36,14 @@ describe('CloudWatchDatasource', () => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
} as TimeSrv;
|
} as TimeSrv;
|
||||||
const backendSrv = {} as BackendSrv;
|
|
||||||
const ctx = {
|
const ctx = {
|
||||||
backendSrv,
|
|
||||||
templateSrv,
|
templateSrv,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.ds = new CloudWatchDatasource(instanceSettings, backendSrv, templateSrv, timeSrv);
|
ctx.ds = new CloudWatchDatasource(instanceSettings, templateSrv, timeSrv);
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When performing CloudWatch query', () => {
|
describe('When performing CloudWatch query', () => {
|
||||||
@@ -86,7 +93,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
datasourceRequestMock.mockImplementation(params => {
|
||||||
requestParams = params.data;
|
requestParams = params.data;
|
||||||
return Promise.resolve({ data: response });
|
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', () => {
|
describe('a correct cloudwatch url should be built for each time series in the response', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
datasourceRequestMock.mockImplementation(params => {
|
||||||
requestParams = params.data;
|
requestParams = params.data;
|
||||||
return Promise.resolve({ data: response });
|
return Promise.resolve({ data: response });
|
||||||
});
|
});
|
||||||
@@ -291,7 +298,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(() => {
|
datasourceRequestMock.mockImplementation(() => {
|
||||||
return Promise.reject(backendErrorResponse);
|
return Promise.reject(backendErrorResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -310,10 +317,10 @@ describe('CloudWatchDatasource', () => {
|
|||||||
|
|
||||||
describe('when regions query is used', () => {
|
describe('when regions query is used', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(() => {
|
datasourceRequestMock.mockImplementation(() => {
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
});
|
});
|
||||||
ctx.ds = new CloudWatchDatasource(instanceSettings, backendSrv, templateSrv, timeSrv);
|
ctx.ds = new CloudWatchDatasource(instanceSettings, templateSrv, timeSrv);
|
||||||
ctx.ds.doMetricQueryRequest = jest.fn(() => []);
|
ctx.ds.doMetricQueryRequest = jest.fn(() => []);
|
||||||
});
|
});
|
||||||
describe('and region param is left out', () => {
|
describe('and region param is left out', () => {
|
||||||
@@ -441,7 +448,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
datasourceRequestMock.mockImplementation(params => {
|
||||||
return Promise.resolve({ data: response });
|
return Promise.resolve({ data: response });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -511,7 +518,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
datasourceRequestMock.mockImplementation(params => {
|
||||||
requestParams = params.data;
|
requestParams = params.data;
|
||||||
return Promise.resolve({ data: {} });
|
return Promise.resolve({ data: {} });
|
||||||
});
|
});
|
||||||
@@ -643,7 +650,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
scenario.setup = async (setupCallback: any) => {
|
scenario.setup = async (setupCallback: any) => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await setupCallback();
|
await setupCallback();
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(args => {
|
datasourceRequestMock.mockImplementation(args => {
|
||||||
scenario.request = args.data;
|
scenario.request = args.data;
|
||||||
return Promise.resolve({ data: scenario.requestResponse });
|
return Promise.resolve({ data: scenario.requestResponse });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,16 +3,23 @@ import { dateMath, Field } from '@grafana/data';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ElasticDatasource } from './datasource';
|
import { ElasticDatasource } from './datasource';
|
||||||
import { toUtc, dateTime } from '@grafana/data';
|
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 { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||||
import { ElasticsearchOptions } from './types';
|
import { ElasticsearchOptions } from './types';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
getBackendSrv: () => backendSrv,
|
||||||
|
}));
|
||||||
|
|
||||||
describe('ElasticDatasource', function(this: any) {
|
describe('ElasticDatasource', function(this: any) {
|
||||||
const backendSrv: any = {
|
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||||
datasourceRequest: jest.fn(),
|
|
||||||
};
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
const $rootScope = {
|
const $rootScope = {
|
||||||
$on: jest.fn(),
|
$on: jest.fn(),
|
||||||
@@ -45,17 +52,11 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
|
|
||||||
const ctx = {
|
const ctx = {
|
||||||
$rootScope,
|
$rootScope,
|
||||||
backendSrv,
|
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
function createDatasource(instanceSettings: DataSourceInstanceSettings<ElasticsearchOptions>) {
|
function createDatasource(instanceSettings: DataSourceInstanceSettings<ElasticsearchOptions>) {
|
||||||
instanceSettings.jsonData = instanceSettings.jsonData || ({} as ElasticsearchOptions);
|
instanceSettings.jsonData = instanceSettings.jsonData || ({} as ElasticsearchOptions);
|
||||||
ctx.ds = new ElasticDatasource(
|
ctx.ds = new ElasticDatasource(instanceSettings, templateSrv as TemplateSrv, timeSrv as TimeSrv);
|
||||||
instanceSettings,
|
|
||||||
backendSrv as BackendSrv,
|
|
||||||
templateSrv as TemplateSrv,
|
|
||||||
timeSrv as TimeSrv
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('When testing datasource with index pattern', () => {
|
describe('When testing datasource with index pattern', () => {
|
||||||
@@ -69,7 +70,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
|
|
||||||
it('should translate index pattern to current day', () => {
|
it('should translate index pattern to current day', () => {
|
||||||
let requestOptions: any;
|
let requestOptions: any;
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
return Promise.resolve({ data: {} });
|
return Promise.resolve({ data: {} });
|
||||||
});
|
});
|
||||||
@@ -91,7 +92,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
jsonData: { interval: 'Daily', esVersion: 2 } as ElasticsearchOptions,
|
jsonData: { interval: 'Daily', esVersion: 2 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
@@ -165,7 +166,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
} as ElasticsearchOptions,
|
} as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
return Promise.resolve(logsResponse);
|
return Promise.resolve(logsResponse);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -225,7 +226,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
jsonData: { esVersion: 2 } as ElasticsearchOptions,
|
jsonData: { esVersion: 2 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
return Promise.resolve({ data: { responses: [] } });
|
return Promise.resolve({ data: { responses: [] } });
|
||||||
});
|
});
|
||||||
@@ -266,7 +267,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
jsonData: { esVersion: 50 } as ElasticsearchOptions,
|
jsonData: { esVersion: 50 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
metricbeat: {
|
metricbeat: {
|
||||||
@@ -362,7 +363,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
jsonData: { esVersion: 70 } as ElasticsearchOptions,
|
jsonData: { esVersion: 70 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
'genuine.es7._mapping.response': {
|
'genuine.es7._mapping.response': {
|
||||||
@@ -515,7 +516,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
return Promise.resolve({ data: { responses: [] } });
|
return Promise.resolve({ data: { responses: [] } });
|
||||||
});
|
});
|
||||||
@@ -558,7 +559,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { IndexPattern } from './index_pattern';
|
|||||||
import { ElasticQueryBuilder } from './query_builder';
|
import { ElasticQueryBuilder } from './query_builder';
|
||||||
import { toUtc } from '@grafana/data';
|
import { toUtc } from '@grafana/data';
|
||||||
import * as queryDef from './query_def';
|
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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery } from './types';
|
import { DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery } from './types';
|
||||||
@@ -36,7 +36,6 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
instanceSettings: DataSourceInstanceSettings<ElasticsearchOptions>,
|
instanceSettings: DataSourceInstanceSettings<ElasticsearchOptions>,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv,
|
private templateSrv: TemplateSrv,
|
||||||
private timeSrv: TimeSrv
|
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) {
|
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 annotation = options.annotation;
|
||||||
const timeField = annotation.timeField || '@timestamp';
|
const timeField = annotation.timeField || '@timestamp';
|
||||||
const timeEndField = annotation.timeEndField || null;
|
const timeEndField = annotation.timeEndField || null;
|
||||||
@@ -511,20 +510,20 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
|
|
||||||
metricFindQuery(query: any) {
|
metricFindQuery(query: any) {
|
||||||
query = angular.fromJson(query);
|
query = angular.fromJson(query);
|
||||||
if (!query) {
|
if (query) {
|
||||||
return Promise.resolve([]);
|
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') {
|
return Promise.resolve([]);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTagKeys() {
|
getTagKeys() {
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
import Datasource from '../datasource';
|
import Datasource from '../datasource';
|
||||||
import { DataFrame, toUtc } from '@grafana/data';
|
import { DataFrame, toUtc } from '@grafana/data';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
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', () => {
|
describe('AppInsightsDatasource', () => {
|
||||||
|
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||||
|
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
backendSrv: {},
|
|
||||||
templateSrv: new TemplateSrv(),
|
templateSrv: new TemplateSrv(),
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
ctx.instanceSettings = {
|
ctx.instanceSettings = {
|
||||||
jsonData: { appInsightsAppId: '3ad4400f-ea7d-465d-a8fb-43fb20555d85' },
|
jsonData: { appInsightsAppId: '3ad4400f-ea7d-465d-a8fb-43fb20555d85' },
|
||||||
url: 'http://appinsightsapi',
|
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', () => {
|
describe('When performing testDatasource', () => {
|
||||||
@@ -38,9 +46,9 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => {
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return success status', () => {
|
it('should return success status', () => {
|
||||||
@@ -63,9 +71,9 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error status and a detailed error message', () => {
|
it('should return error status and a detailed error message', () => {
|
||||||
@@ -91,9 +99,9 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error status and a detailed error message', () => {
|
it('should return error status and a detailed error message', () => {
|
||||||
@@ -151,7 +159,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
expect(options.url).toContain('/api/tsdb/query');
|
expect(options.url).toContain('/api/tsdb/query');
|
||||||
expect(options.data.queries.length).toBe(1);
|
expect(options.data.queries.length).toBe(1);
|
||||||
expect(options.data.queries[0].refId).toBe('A');
|
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.valueColumn).toEqual('max');
|
||||||
expect(options.data.queries[0].appInsights.segmentColumn).toBeUndefined();
|
expect(options.data.queries[0].appInsights.segmentColumn).toBeUndefined();
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
@@ -194,7 +202,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
options.targets[0].appInsights.segmentColumn = 'partition';
|
options.targets[0].appInsights.segmentColumn = 'partition';
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
expect(options.url).toContain('/api/tsdb/query');
|
expect(options.url).toContain('/api/tsdb/query');
|
||||||
expect(options.data.queries.length).toBe(1);
|
expect(options.data.queries.length).toBe(1);
|
||||||
expect(options.data.queries[0].refId).toBe('A');
|
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.valueColumn).toEqual('max');
|
||||||
expect(options.data.queries[0].appInsights.segmentColumn).toEqual('partition');
|
expect(options.data.queries[0].appInsights.segmentColumn).toEqual('partition');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
@@ -257,14 +265,14 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
expect(options.url).toContain('/api/tsdb/query');
|
expect(options.url).toContain('/api/tsdb/query');
|
||||||
expect(options.data.queries.length).toBe(1);
|
expect(options.data.queries.length).toBe(1);
|
||||||
expect(options.data.queries[0].refId).toBe('A');
|
expect(options.data.queries[0].refId).toBe('A');
|
||||||
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
||||||
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a single datapoint', () => {
|
it('should return a single datapoint', () => {
|
||||||
@@ -300,14 +308,14 @@ describe('AppInsightsDatasource', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
options.targets[0].appInsights.timeGrain = 'PT30M';
|
options.targets[0].appInsights.timeGrain = 'PT30M';
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
expect(options.url).toContain('/api/tsdb/query');
|
expect(options.url).toContain('/api/tsdb/query');
|
||||||
expect(options.data.queries[0].refId).toBe('A');
|
expect(options.data.queries[0].refId).toBe('A');
|
||||||
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
||||||
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
||||||
expect(options.data.queries[0].appInsights.timeGrain).toBe('PT30M');
|
expect(options.data.queries[0].appInsights.timeGrain).toBe('PT30M');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
@@ -355,13 +363,13 @@ describe('AppInsightsDatasource', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
options.targets[0].appInsights.dimension = 'client/city';
|
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.url).toContain('/api/tsdb/query');
|
||||||
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
|
||||||
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
|
||||||
expect(options.data.queries[0].appInsights.dimension).toBe('client/city');
|
expect(options.data.queries[0].appInsights.dimension).toBe('client/city');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
@@ -397,10 +405,10 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('/metrics/metadata');
|
expect(options.url).toContain('/metrics/metadata');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of metric names', () => {
|
it('should return a list of metric names', () => {
|
||||||
@@ -435,10 +443,10 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('/metrics/metadata');
|
expect(options.url).toContain('/metrics/metadata');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of group bys', () => {
|
it('should return a list of group bys', () => {
|
||||||
@@ -463,10 +471,10 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('/metrics/metadata');
|
expect(options.url).toContain('/metrics/metadata');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of metric names', () => {
|
it('should return a list of metric names', () => {
|
||||||
@@ -501,10 +509,10 @@ describe('AppInsightsDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('/metrics/metadata');
|
expect(options.url).toContain('/metrics/metadata');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of group bys', () => {
|
it('should return a list of group bys', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TimeSeries, toDataFrame } from '@grafana/data';
|
import { TimeSeries, toDataFrame } from '@grafana/data';
|
||||||
import { DataQueryRequest, DataQueryResponseData, DataSourceInstanceSettings } 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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
@@ -21,11 +21,7 @@ export default class AppInsightsDatasource {
|
|||||||
logAnalyticsColumns: { [key: string]: LogAnalyticsColumn[] } = {};
|
logAnalyticsColumns: { [key: string]: LogAnalyticsColumn[] } = {};
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>, private templateSrv: TemplateSrv) {
|
||||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv
|
|
||||||
) {
|
|
||||||
this.id = instanceSettings.id;
|
this.id = instanceSettings.id;
|
||||||
this.applicationId = instanceSettings.jsonData.appInsightsAppId;
|
this.applicationId = instanceSettings.jsonData.appInsightsAppId;
|
||||||
this.baseUrl = `/appinsights/${this.version}/apps/${this.applicationId}`;
|
this.baseUrl = `/appinsights/${this.version}/apps/${this.applicationId}`;
|
||||||
@@ -119,7 +115,7 @@ export default class AppInsightsDatasource {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await this.backendSrv.datasourceRequest({
|
const { data } = await getBackendSrv().datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
@@ -228,8 +224,8 @@ export default class AppInsightsDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doRequest(url: any, maxRetries = 1) {
|
doRequest(url: any, maxRetries = 1): Promise<any> {
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: this.url + url,
|
url: this.url + url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -4,10 +4,22 @@ import FakeSchemaData from './__mocks__/schema';
|
|||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { KustoSchema } from '../types';
|
import { KustoSchema } from '../types';
|
||||||
import { toUtc } from '@grafana/data';
|
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', () => {
|
describe('AzureLogAnalyticsDatasource', () => {
|
||||||
|
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
datasourceRequestMock.mockImplementation(jest.fn());
|
||||||
|
});
|
||||||
|
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
backendSrv: {},
|
|
||||||
templateSrv: new TemplateSrv(),
|
templateSrv: new TemplateSrv(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +29,7 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
url: 'http://azureloganalyticsapi',
|
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', () => {
|
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.tenantId = 'xxx';
|
||||||
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
||||||
ctx.instanceSettings.jsonData.azureLogAnalyticsSameAs = true;
|
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) {
|
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
|
||||||
workspacesUrl = options.url;
|
workspacesUrl = options.url;
|
||||||
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
||||||
@@ -66,7 +78,7 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
azureLogAnalyticsUrl = options.url;
|
azureLogAnalyticsUrl = options.url;
|
||||||
return Promise.resolve({ data: tableResponseWithOneColumn, status: 200 });
|
return Promise.resolve({ data: tableResponseWithOneColumn, status: 200 });
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
|
await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
|
||||||
});
|
});
|
||||||
@@ -94,9 +106,7 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
ctx.instanceSettings.jsonData.logAnalyticsSubscriptionId = 'xxx';
|
ctx.instanceSettings.jsonData.logAnalyticsSubscriptionId = 'xxx';
|
||||||
ctx.instanceSettings.jsonData.logAnalyticsTenantId = 'xxx';
|
ctx.instanceSettings.jsonData.logAnalyticsTenantId = 'xxx';
|
||||||
ctx.instanceSettings.jsonData.logAnalyticsClientId = 'xxx';
|
ctx.instanceSettings.jsonData.logAnalyticsClientId = 'xxx';
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => Promise.reject(error));
|
||||||
return Promise.reject(error);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error status and a detailed error message', () => {
|
it('should return error status and a detailed error message', () => {
|
||||||
@@ -166,10 +176,10 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
describe('in time series format', () => {
|
describe('in time series format', () => {
|
||||||
describe('and the data is valid (has time, metric and value columns)', () => {
|
describe('and the data is valid (has time, metric and value columns)', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('query=AzureActivity');
|
expect(options.url).toContain('query=AzureActivity');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
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');
|
expect(options.url).toContain('query=AzureActivity');
|
||||||
return Promise.resolve({ data: invalidResponse, status: 200 });
|
return Promise.resolve({ data: invalidResponse, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an exception', () => {
|
it('should throw an exception', () => {
|
||||||
@@ -222,10 +233,10 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
describe('in tableformat', () => {
|
describe('in tableformat', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
options.targets[0].azureLogAnalytics.resultFormat = 'table';
|
options.targets[0].azureLogAnalytics.resultFormat = 'table';
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('query=AzureActivity');
|
expect(options.url).toContain('query=AzureActivity');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of columns and rows', () => {
|
it('should return a list of columns and rows', () => {
|
||||||
@@ -249,10 +260,10 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
|
|
||||||
describe('When performing getSchema', () => {
|
describe('When performing getSchema', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('metadata');
|
expect(options.url).toContain('metadata');
|
||||||
return Promise.resolve({ data: FakeSchemaData.getlogAnalyticsFakeMetadata(), status: 200 });
|
return Promise.resolve({ data: FakeSchemaData.getlogAnalyticsFakeMetadata(), status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a schema with a table and rows', () => {
|
it('should return a schema with a table and rows', () => {
|
||||||
@@ -302,13 +313,13 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
let queryResults: any[];
|
let queryResults: any[];
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
|
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
|
||||||
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: tableResponseWithOneColumn, status: 200 });
|
return Promise.resolve({ data: tableResponseWithOneColumn, status: 200 });
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
queryResults = await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
|
queryResults = await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
|
||||||
});
|
});
|
||||||
@@ -364,13 +375,13 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
let annotationResults: any[];
|
let annotationResults: any[];
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
|
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
|
||||||
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
return Promise.resolve({ data: workspaceResponse, status: 200 });
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: tableResponse, status: 200 });
|
return Promise.resolve({ data: tableResponse, status: 200 });
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
annotationResults = await ctx.ds.annotationQuery({
|
annotationResults = await ctx.ds.annotationQuery({
|
||||||
annotation: {
|
annotation: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder
|
|||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
import { AzureMonitorQuery, AzureDataSourceJsonData } from '../types';
|
import { AzureMonitorQuery, AzureDataSourceJsonData } from '../types';
|
||||||
import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
|
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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
export default class AzureLogAnalyticsDatasource {
|
export default class AzureLogAnalyticsDatasource {
|
||||||
@@ -18,7 +18,6 @@ export default class AzureLogAnalyticsDatasource {
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv
|
private templateSrv: TemplateSrv
|
||||||
) {
|
) {
|
||||||
this.id = instanceSettings.id;
|
this.id = instanceSettings.id;
|
||||||
@@ -230,8 +229,8 @@ export default class AzureLogAnalyticsDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doRequest(url: string, maxRetries = 1) {
|
doRequest(url: string, maxRetries = 1): Promise<any> {
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: this.url + url,
|
url: this.url + url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -2,21 +2,28 @@ import AzureMonitorDatasource from '../datasource';
|
|||||||
|
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { toUtc, DataFrame } from '@grafana/data';
|
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', () => {
|
describe('AzureMonitorDatasource', () => {
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
backendSrv: {},
|
|
||||||
templateSrv: new TemplateSrv(),
|
templateSrv: new TemplateSrv(),
|
||||||
};
|
};
|
||||||
|
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
ctx.instanceSettings = {
|
ctx.instanceSettings = {
|
||||||
url: 'http://azuremonitor.com',
|
url: 'http://azuremonitor.com',
|
||||||
jsonData: { subscriptionId: '9935389e-9122-4ef9-95f9-1513dd24753f' },
|
jsonData: { subscriptionId: '9935389e-9122-4ef9-95f9-1513dd24753f' },
|
||||||
cloudName: 'azuremonitor',
|
cloudName: 'azuremonitor',
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When performing testDatasource', () => {
|
describe('When performing testDatasource', () => {
|
||||||
@@ -35,9 +42,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.instanceSettings.jsonData.tenantId = 'xxx';
|
ctx.instanceSettings.jsonData.tenantId = 'xxx';
|
||||||
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => Promise.reject(error));
|
||||||
return Promise.reject(error);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error status and a detailed error message', () => {
|
it('should return error status and a detailed error message', () => {
|
||||||
@@ -62,9 +67,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.instanceSettings.jsonData.tenantId = 'xxx';
|
ctx.instanceSettings.jsonData.tenantId = 'xxx';
|
||||||
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
ctx.instanceSettings.jsonData.clientId = 'xxx';
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => Promise.resolve({ data: response, status: 200 }));
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return success status', () => {
|
it('should return success status', () => {
|
||||||
@@ -124,10 +127,10 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('/api/tsdb/query');
|
expect(options.url).toContain('/api/tsdb/query');
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
@@ -157,9 +160,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => Promise.resolve(response));
|
||||||
return Promise.resolve(response);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of subscriptions', () => {
|
it('should return a list of subscriptions', () => {
|
||||||
@@ -183,9 +184,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
|
||||||
return Promise.resolve(response);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of resource groups', () => {
|
it('should return a list of resource groups', () => {
|
||||||
@@ -209,10 +208,10 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
expect(options.url).toContain('11112222-eeee-4949-9b2d-9106972f9123');
|
expect(options.url).toContain('11112222-eeee-4949-9b2d-9106972f9123');
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of resource groups', () => {
|
it('should return a list of resource groups', () => {
|
||||||
@@ -243,12 +242,12 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||||
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of namespaces', () => {
|
it('should return a list of namespaces', () => {
|
||||||
@@ -277,12 +276,12 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
||||||
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of namespaces', () => {
|
it('should return a list of namespaces', () => {
|
||||||
@@ -315,12 +314,12 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||||
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of resource names', () => {
|
it('should return a list of resource names', () => {
|
||||||
@@ -353,12 +352,12 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
||||||
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of resource names', () => {
|
it('should return a list of resource names', () => {
|
||||||
@@ -397,7 +396,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||||
expect(options.url).toBe(
|
expect(options.url).toBe(
|
||||||
@@ -406,7 +405,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
'metricdefinitions?api-version=2018-01-01&metricnamespace=default'
|
'metricdefinitions?api-version=2018-01-01&metricnamespace=default'
|
||||||
);
|
);
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of metric names', () => {
|
it('should return a list of metric names', () => {
|
||||||
@@ -446,7 +445,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
||||||
expect(options.url).toBe(
|
expect(options.url).toBe(
|
||||||
@@ -455,7 +454,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
'metricdefinitions?api-version=2018-01-01&metricnamespace=default'
|
'metricdefinitions?api-version=2018-01-01&metricnamespace=default'
|
||||||
);
|
);
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of metric names', () => {
|
it('should return a list of metric names', () => {
|
||||||
@@ -497,7 +496,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||||
expect(options.url).toBe(
|
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'
|
'/nodeapp/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview'
|
||||||
);
|
);
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of metric names', () => {
|
it('should return a list of metric names', () => {
|
||||||
@@ -545,7 +544,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
||||||
expect(options.url).toBe(
|
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'
|
'/nodeapp/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview'
|
||||||
);
|
);
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of metric namespaces', () => {
|
it('should return a list of metric namespaces', () => {
|
||||||
@@ -601,9 +600,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
|
||||||
return Promise.resolve(response);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return list of Resource Groups', () => {
|
it('should return list of Resource Groups', () => {
|
||||||
@@ -625,9 +622,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = () => {
|
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
|
||||||
return Promise.resolve(response);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return list of Resource Groups', () => {
|
it('should return list of Resource Groups', () => {
|
||||||
@@ -674,12 +669,12 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||||
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
expect(options.url).toBe(baseUrl + '/nodesapp/resources?api-version=2018-01-01');
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return list of Metric Definitions with no duplicates and no unsupported namespaces', () => {
|
it('should return list of Metric Definitions with no duplicates and no unsupported namespaces', () => {
|
||||||
@@ -725,12 +720,12 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||||
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return list of Resource Names', () => {
|
it('should return list of Resource Names', () => {
|
||||||
@@ -763,12 +758,12 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
||||||
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
expect(options.url).toBe(baseUrl + '/nodeapp/resources?api-version=2018-01-01');
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return list of Resource Names', () => {
|
it('should return list of Resource Names', () => {
|
||||||
@@ -828,7 +823,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
||||||
const expected =
|
const expected =
|
||||||
@@ -837,7 +832,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
||||||
expect(options.url).toBe(expected);
|
expect(options.url).toBe(expected);
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return list of Metric Definitions', () => {
|
it('should return list of Metric Definitions', () => {
|
||||||
@@ -900,7 +895,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
||||||
const expected =
|
const expected =
|
||||||
@@ -909,7 +904,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
||||||
expect(options.url).toBe(expected);
|
expect(options.url).toBe(expected);
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return Aggregation metadata for a Metric', () => {
|
it('should return Aggregation metadata for a Metric', () => {
|
||||||
@@ -974,7 +969,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
datasourceRequestMock.mockImplementation((options: { url: string }) => {
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
'http://azuremonitor.com/azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp';
|
||||||
const expected =
|
const expected =
|
||||||
@@ -983,7 +978,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=default';
|
||||||
expect(options.url).toBe(expected);
|
expect(options.url).toBe(expected);
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return dimensions for a Metric that has dimensions', () => {
|
it('should return dimensions for a Metric that has dimensions', () => {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
import { DataQueryRequest, DataQueryResponseData, DataSourceInstanceSettings } from '@grafana/data';
|
import { DataQueryRequest, DataQueryResponseData, DataSourceInstanceSettings } from '@grafana/data';
|
||||||
|
|
||||||
import { TimeSeries, toDataFrame } 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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export default class AzureMonitorDatasource {
|
export default class AzureMonitorDatasource {
|
||||||
apiVersion = '2018-01-01';
|
apiVersion = '2018-01-01';
|
||||||
@@ -31,7 +31,6 @@ export default class AzureMonitorDatasource {
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv
|
private templateSrv: TemplateSrv
|
||||||
) {
|
) {
|
||||||
this.id = instanceSettings.id;
|
this.id = instanceSettings.id;
|
||||||
@@ -108,7 +107,7 @@ export default class AzureMonitorDatasource {
|
|||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await this.backendSrv.datasourceRequest({
|
const { data } = await getBackendSrv().datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
@@ -434,8 +433,8 @@ export default class AzureMonitorDatasource {
|
|||||||
return field && field.length > 0;
|
return field && field.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
doRequest(url: string, maxRetries = 1) {
|
doRequest(url: string, maxRetries = 1): Promise<any> {
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: this.url + url,
|
url: this.url + url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import { MonitorConfig } from './MonitorConfig';
|
import { MonitorConfig } from './MonitorConfig';
|
||||||
import { AnalyticsConfig } from './AnalyticsConfig';
|
import { AnalyticsConfig } from './AnalyticsConfig';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
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 { InsightsConfig } from './InsightsConfig';
|
||||||
import ResponseParser from '../azure_monitor/response_parser';
|
import ResponseParser from '../azure_monitor/response_parser';
|
||||||
import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData, AzureDataSourceSettings } from '../types';
|
import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData, AzureDataSourceSettings } from '../types';
|
||||||
@@ -38,7 +38,6 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
|||||||
logAnalyticsSubscriptionId: '',
|
logAnalyticsSubscriptionId: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.backendSrv = getBackendSrv();
|
|
||||||
this.templateSrv = new TemplateSrv();
|
this.templateSrv = new TemplateSrv();
|
||||||
if (this.props.options.id) {
|
if (this.props.options.id) {
|
||||||
updateDatasourcePluginOption(this.props, 'url', '/api/datasources/proxy/' + 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;
|
initPromise: CancelablePromise<any> = null;
|
||||||
backendSrv: BackendSrv = null;
|
|
||||||
templateSrv: TemplateSrv = null;
|
templateSrv: TemplateSrv = null;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -157,7 +155,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onLoadSubscriptions = async (type?: string) => {
|
onLoadSubscriptions = async (type?: string) => {
|
||||||
await this.backendSrv
|
await getBackendSrv()
|
||||||
.put(`/api/datasources/${this.props.options.id}`, this.props.options)
|
.put(`/api/datasources/${this.props.options.id}`, this.props.options)
|
||||||
.then((result: AzureDataSourceSettings) => {
|
.then((result: AzureDataSourceSettings) => {
|
||||||
updateDatasourcePluginOption(this.props, 'version', result.version);
|
updateDatasourcePluginOption(this.props, 'version', result.version);
|
||||||
@@ -173,7 +171,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
|||||||
loadSubscriptions = async (route?: string) => {
|
loadSubscriptions = async (route?: string) => {
|
||||||
const url = `/${route || this.props.options.jsonData.cloudName}/subscriptions?api-version=2019-03-01`;
|
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,
|
url: this.props.options.url + url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
@@ -198,7 +196,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
|||||||
azureMonitorUrl +
|
azureMonitorUrl +
|
||||||
`/${subscriptionId}/providers/Microsoft.OperationalInsights/workspaces?api-version=2017-04-26-preview`;
|
`/${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,
|
url: this.props.options.url + workspaceListUrl,
|
||||||
method: 'GET',
|
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 AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
|
||||||
import { AzureMonitorQuery, AzureDataSourceJsonData } from './types';
|
import { AzureMonitorQuery, AzureDataSourceJsonData } from './types';
|
||||||
import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
|
import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
|
export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
|
||||||
@@ -13,20 +12,12 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
|||||||
azureLogAnalyticsDatasource: AzureLogAnalyticsDatasource;
|
azureLogAnalyticsDatasource: AzureLogAnalyticsDatasource;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>, private templateSrv: TemplateSrv) {
|
||||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv
|
|
||||||
) {
|
|
||||||
super(instanceSettings);
|
super(instanceSettings);
|
||||||
this.azureMonitorDatasource = new AzureMonitorDatasource(instanceSettings, this.backendSrv, this.templateSrv);
|
this.azureMonitorDatasource = new AzureMonitorDatasource(instanceSettings, this.templateSrv);
|
||||||
this.appInsightsDatasource = new AppInsightsDatasource(instanceSettings, this.backendSrv, this.templateSrv);
|
this.appInsightsDatasource = new AppInsightsDatasource(instanceSettings, this.templateSrv);
|
||||||
|
|
||||||
this.azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(
|
this.azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(instanceSettings, this.templateSrv);
|
||||||
instanceSettings,
|
|
||||||
this.backendSrv,
|
|
||||||
this.templateSrv
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(options: DataQueryRequest<AzureMonitorQuery>) {
|
async query(options: DataQueryRequest<AzureMonitorQuery>) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
class GrafanaDatasource {
|
class GrafanaDatasource {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private backendSrv: BackendSrv, private templateSrv: TemplateSrv) {}
|
constructor(private templateSrv: TemplateSrv) {}
|
||||||
|
|
||||||
query(options: any) {
|
query(options: any) {
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.get('/api/tsdb/testdata/random-walk', {
|
.get('/api/tsdb/testdata/random-walk', {
|
||||||
from: options.range.from.valueOf(),
|
from: options.range.from.valueOf(),
|
||||||
to: options.range.to.valueOf(),
|
to: options.range.to.valueOf(),
|
||||||
@@ -76,7 +76,7 @@ class GrafanaDatasource {
|
|||||||
params.tags = tags;
|
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 { 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', () => {
|
describe('grafana data source', () => {
|
||||||
|
const getMock = jest.spyOn(backendSrv, 'get');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
describe('when executing an annotations query', () => {
|
describe('when executing an annotations query', () => {
|
||||||
let calledBackendSrvParams: any;
|
let calledBackendSrvParams: any;
|
||||||
const backendSrvStub = {
|
let templateSrvStub: any;
|
||||||
get: (url: string, options: any) => {
|
let ds: GrafanaDatasource;
|
||||||
|
beforeEach(() => {
|
||||||
|
getMock.mockImplementation((url: string, options: any) => {
|
||||||
calledBackendSrvParams = options;
|
calledBackendSrvParams = options;
|
||||||
return q.resolve([]);
|
return Promise.resolve([]);
|
||||||
},
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const templateSrvStub = {
|
templateSrvStub = {
|
||||||
replace: (val: string) => {
|
replace: (val: string) => {
|
||||||
return val.replace('$var2', 'replaced__delimiter__replaced2').replace('$var', 'replaced');
|
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', () => {
|
describe('with tags that have template variables', () => {
|
||||||
const options = setupAnnotationQueryOptions({ tags: ['tag1:$var'] });
|
const options = setupAnnotationQueryOptions({ tags: ['tag1:$var'] });
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
|
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
|
||||||
import gfunc from './gfunc';
|
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';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
//Types
|
//Types
|
||||||
import { GraphiteOptions, GraphiteQuery, GraphiteType } from './types';
|
import { GraphiteOptions, GraphiteQuery, GraphiteType } from './types';
|
||||||
@@ -30,7 +30,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
|||||||
_seriesRefLetters: string;
|
_seriesRefLetters: string;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(instanceSettings: any, private backendSrv: BackendSrv, private templateSrv: TemplateSrv) {
|
constructor(instanceSettings: any, private templateSrv: TemplateSrv) {
|
||||||
super(instanceSettings);
|
super(instanceSettings);
|
||||||
this.basicAuth = instanceSettings.basicAuth;
|
this.basicAuth = instanceSettings.basicAuth;
|
||||||
this.url = instanceSettings.url;
|
this.url = instanceSettings.url;
|
||||||
@@ -571,7 +571,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
|||||||
options.url = this.url + options.url;
|
options.url = this.url + options.url;
|
||||||
options.inspect = { type: 'graphite' };
|
options.inspect = { type: 'graphite' };
|
||||||
|
|
||||||
return this.backendSrv.datasourceRequest(options);
|
return getBackendSrv().datasourceRequest(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildGraphiteParams(options: any, scopedVars: ScopedVars): string[] {
|
buildGraphiteParams(options: any, scopedVars: ScopedVars): string[] {
|
||||||
|
|||||||
@@ -3,19 +3,26 @@ import _ from 'lodash';
|
|||||||
|
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { dateTime } from '@grafana/data';
|
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', () => {
|
describe('graphiteDatasource', () => {
|
||||||
|
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||||
|
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
backendSrv: {},
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
templateSrv: new TemplateSrv(),
|
templateSrv: new TemplateSrv(),
|
||||||
instanceSettings: { url: 'url', name: 'graphiteProd', jsonData: {} },
|
instanceSettings: { url: 'url', name: 'graphiteProd', jsonData: {} },
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
ctx.instanceSettings.url = '/api/datasources/proxy/1';
|
ctx.instanceSettings.url = '/api/datasources/proxy/1';
|
||||||
// @ts-ignore
|
ctx.ds = new GraphiteDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||||
ctx.ds = new GraphiteDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When querying graphite with one target using query editor target spec', () => {
|
describe('When querying graphite with one target using query editor target spec', () => {
|
||||||
@@ -31,7 +38,7 @@ describe('graphiteDatasource', () => {
|
|||||||
let requestOptions: any;
|
let requestOptions: any;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: [
|
data: [
|
||||||
@@ -44,7 +51,7 @@ describe('graphiteDatasource', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
await ctx.ds.query(query).then((data: any) => {
|
await ctx.ds.query(query).then((data: any) => {
|
||||||
results = data;
|
results = data;
|
||||||
@@ -115,10 +122,9 @@ describe('graphiteDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
|
|
||||||
await ctx.ds.annotationQuery(options).then((data: any) => {
|
await ctx.ds.annotationQuery(options).then((data: any) => {
|
||||||
results = data;
|
results = data;
|
||||||
});
|
});
|
||||||
@@ -145,9 +151,9 @@ describe('graphiteDatasource', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
};
|
});
|
||||||
|
|
||||||
ctx.ds.annotationQuery(options).then((data: any) => {
|
ctx.ds.annotationQuery(options).then((data: any) => {
|
||||||
results = data;
|
results = data;
|
||||||
@@ -263,12 +269,12 @@ describe('graphiteDatasource', () => {
|
|||||||
let requestOptions: any;
|
let requestOptions: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: ['backend_01', 'backend_02'],
|
data: ['backend_01', 'backend_02'],
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate tags query', () => {
|
it('should generate tags query', () => {
|
||||||
@@ -390,7 +396,6 @@ describe('graphiteDatasource', () => {
|
|||||||
function accessScenario(name: string, url: string, fn: any) {
|
function accessScenario(name: string, url: string, fn: any) {
|
||||||
describe('access scenario ' + name, () => {
|
describe('access scenario ' + name, () => {
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
backendSrv: {},
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
templateSrv: new TemplateSrv(),
|
templateSrv: new TemplateSrv(),
|
||||||
instanceSettings: { url: 'url', name: 'graphiteProd', jsonData: {} },
|
instanceSettings: { url: 'url', name: 'graphiteProd', jsonData: {} },
|
||||||
@@ -405,8 +410,7 @@ function accessScenario(name: string, url: string, fn: any) {
|
|||||||
|
|
||||||
it('tracing headers should be added', () => {
|
it('tracing headers should be added', () => {
|
||||||
ctx.instanceSettings.url = url;
|
ctx.instanceSettings.url = url;
|
||||||
// @ts-ignore
|
const ds = new GraphiteDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||||
const ds = new GraphiteDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.templateSrv);
|
|
||||||
ds.addTracingHeaders(httpOptions, options);
|
ds.addTracingHeaders(httpOptions, options);
|
||||||
fn(httpOptions);
|
fn(httpOptions);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import InfluxQueryModel from './influx_query_model';
|
|||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
import { InfluxQueryBuilder } from './query_builder';
|
import { InfluxQueryBuilder } from './query_builder';
|
||||||
import { InfluxQuery, InfluxOptions } from './types';
|
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';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxOptions> {
|
export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxOptions> {
|
||||||
@@ -23,11 +23,7 @@ export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxO
|
|||||||
httpMode: string;
|
httpMode: string;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>, private templateSrv: TemplateSrv) {
|
||||||
instanceSettings: DataSourceInstanceSettings<InfluxOptions>,
|
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv
|
|
||||||
) {
|
|
||||||
super(instanceSettings);
|
super(instanceSettings);
|
||||||
this.type = 'influxdb';
|
this.type = 'influxdb';
|
||||||
this.urls = _.map(instanceSettings.url.split(','), url => {
|
this.urls = _.map(instanceSettings.url.split(','), url => {
|
||||||
@@ -208,7 +204,7 @@ export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxO
|
|||||||
metricFindQuery(query: string, options?: any) {
|
metricFindQuery(query: string, options?: any) {
|
||||||
const interpolated = this.templateSrv.replace(query, null, 'regex');
|
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 = {}) {
|
getTagKeys(options: any = {}) {
|
||||||
@@ -320,28 +316,30 @@ export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxO
|
|||||||
req.headers['Content-type'] = 'application/x-www-form-urlencoded';
|
req.headers['Content-type'] = 'application/x-www-form-urlencoded';
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv.datasourceRequest(req).then(
|
return getBackendSrv()
|
||||||
(result: any) => {
|
.datasourceRequest(req)
|
||||||
return result.data;
|
.then(
|
||||||
},
|
(result: any) => {
|
||||||
(err: any) => {
|
return result.data;
|
||||||
if (err.status !== 0 || err.status >= 300) {
|
},
|
||||||
if (err.data && err.data.error) {
|
(err: any) => {
|
||||||
throw {
|
if (err.status !== 0 || err.status >= 300) {
|
||||||
message: 'InfluxDB Error: ' + err.data.error,
|
if (err.data && err.data.error) {
|
||||||
data: err.data,
|
throw {
|
||||||
config: err.config,
|
message: 'InfluxDB Error: ' + err.data.error,
|
||||||
};
|
data: err.data,
|
||||||
} else {
|
config: err.config,
|
||||||
throw {
|
};
|
||||||
message: 'Network Error: ' + err.statusText + '(' + err.status + ')',
|
} else {
|
||||||
data: err.data,
|
throw {
|
||||||
config: err.config,
|
message: 'Network Error: ' + err.statusText + '(' + err.status + ')',
|
||||||
};
|
data: err.data,
|
||||||
|
config: err.config,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTimeFilter(options: any) {
|
getTimeFilter(options: any) {
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
import InfluxDatasource from '../datasource';
|
import InfluxDatasource from '../datasource';
|
||||||
|
|
||||||
import { TemplateSrvStub } from 'test/specs/helpers';
|
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', () => {
|
describe('InfluxDataSource', () => {
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
backendSrv: {},
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
templateSrv: new TemplateSrvStub(),
|
templateSrv: new TemplateSrvStub(),
|
||||||
instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'GET' } },
|
instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'GET' } },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
ctx.instanceSettings.url = '/api/datasources/proxy/1';
|
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', () => {
|
describe('When issuing metricFindQuery', () => {
|
||||||
@@ -26,7 +34,7 @@ describe('InfluxDataSource', () => {
|
|||||||
let requestQuery: any, requestMethod: any, requestData: any;
|
let requestQuery: any, requestMethod: any, requestData: any;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ctx.backendSrv.datasourceRequest = (req: any) => {
|
datasourceRequestMock.mockImplementation((req: any) => {
|
||||||
requestMethod = req.method;
|
requestMethod = req.method;
|
||||||
requestQuery = req.params.q;
|
requestQuery = req.params.q;
|
||||||
requestData = req.data;
|
requestData = req.data;
|
||||||
@@ -43,7 +51,7 @@ describe('InfluxDataSource', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
await ctx.ds.metricFindQuery(query, queryOptions).then(() => {});
|
await ctx.ds.metricFindQuery(query, queryOptions).then(() => {});
|
||||||
});
|
});
|
||||||
@@ -60,60 +68,59 @@ describe('InfluxDataSource', () => {
|
|||||||
expect(requestData).toBeNull();
|
expect(requestData).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('InfluxDataSource in POST query mode', () => {
|
describe('InfluxDataSource in POST query mode', () => {
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
backendSrv: {},
|
//@ts-ignore
|
||||||
//@ts-ignore
|
templateSrv: new TemplateSrvStub(),
|
||||||
templateSrv: new TemplateSrvStub(),
|
instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'POST' } },
|
||||||
instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'POST' } },
|
};
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.instanceSettings.url = '/api/datasources/proxy/1';
|
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', () => {
|
describe('When issuing metricFindQuery', () => {
|
||||||
const query = 'SELECT max(value) FROM measurement';
|
const query = 'SELECT max(value) FROM measurement';
|
||||||
const queryOptions: any = {};
|
const queryOptions: any = {};
|
||||||
let requestMethod: any, requestQueryParameter: any, queryEncoded: any, requestQuery: any;
|
let requestMethod: any, requestQueryParameter: any, queryEncoded: any, requestQuery: any;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ctx.backendSrv.datasourceRequest = (req: any) => {
|
datasourceRequestMock.mockImplementation((req: any) => {
|
||||||
requestMethod = req.method;
|
requestMethod = req.method;
|
||||||
requestQueryParameter = req.params;
|
requestQueryParameter = req.params;
|
||||||
requestQuery = req.data;
|
requestQuery = req.data;
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
results: [
|
results: [
|
||||||
{
|
{
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: 'measurement',
|
name: 'measurement',
|
||||||
columns: ['max'],
|
columns: ['max'],
|
||||||
values: [[1]],
|
values: [[1]],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
queryEncoded = await ctx.ds.serializeParams({ q: query });
|
queryEncoded = await ctx.ds.serializeParams({ q: query });
|
||||||
await ctx.ds.metricFindQuery(query, queryOptions).then(() => {});
|
await ctx.ds.metricFindQuery(query, queryOptions).then(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have the query form urlencoded', () => {
|
it('should have the query form urlencoded', () => {
|
||||||
expect(requestQuery).toBe(queryEncoded);
|
expect(requestQuery).toBe(queryEncoded);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the HTTP POST method', () => {
|
it('should use the HTTP POST method', () => {
|
||||||
expect(requestMethod).toBe('POST');
|
expect(requestMethod).toBe('POST');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not have q as a query parameter', () => {
|
it('should not have q as a query parameter', () => {
|
||||||
expect(requestQueryParameter).not.toHaveProperty('q');
|
expect(requestQueryParameter).not.toHaveProperty('q');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,20 @@ import LokiDatasource, { RangeQueryOptions } from './datasource';
|
|||||||
import { LokiQuery, LokiResultType, LokiResponse, LokiLegacyStreamResponse } from './types';
|
import { LokiQuery, LokiResultType, LokiResponse, LokiLegacyStreamResponse } from './types';
|
||||||
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
||||||
import { AnnotationQueryRequest, DataSourceApi, DataFrame, dateTime, TimeRange } from '@grafana/data';
|
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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { CustomVariable } from 'app/features/templating/custom_variable';
|
import { CustomVariable } from 'app/features/templating/custom_variable';
|
||||||
import { makeMockLokiDatasource } from './mocks';
|
import { makeMockLokiDatasource } from './mocks';
|
||||||
import { ExploreMode } from 'app/types';
|
import { ExploreMode } from 'app/types';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import omit from 'lodash/omit';
|
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', () => {
|
describe('LokiDatasource', () => {
|
||||||
const instanceSettings: any = {
|
const instanceSettings: any = {
|
||||||
@@ -42,8 +49,10 @@ describe('LokiDatasource', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const backendSrvMock = { datasourceRequest: jest.fn() };
|
beforeEach(() => {
|
||||||
const backendSrv = (backendSrvMock as unknown) as BackendSrv;
|
jest.clearAllMocks();
|
||||||
|
datasourceRequestMock.mockImplementation(() => Promise.resolve());
|
||||||
|
});
|
||||||
|
|
||||||
const templateSrvMock = ({
|
const templateSrvMock = ({
|
||||||
getAdhocFilters: (): any[] => [],
|
getAdhocFilters: (): any[] => [],
|
||||||
@@ -56,7 +65,7 @@ describe('LokiDatasource', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||||
adjustIntervalSpy = jest.spyOn(ds, 'adjustInterval');
|
adjustIntervalSpy = jest.spyOn(ds, 'adjustInterval');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,8 +104,8 @@ describe('LokiDatasource', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||||
backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(legacyTestResp));
|
datasourceRequestMock.mockImplementation(() => Promise.resolve(legacyTestResp));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should try latest endpoint but fall back to legacy endpoint if it cannot be reached', async () => {
|
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', () => {
|
describe('when querying', () => {
|
||||||
const testLimit = makeLimitTest(instanceSettings, backendSrvMock, backendSrv, templateSrvMock, legacyTestResp);
|
|
||||||
let ds: LokiDatasource;
|
let ds: LokiDatasource;
|
||||||
|
let testLimit: any;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
testLimit = makeLimitTest(instanceSettings, datasourceRequestMock, templateSrvMock, legacyTestResp);
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||||
backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
|
datasourceRequestMock.mockImplementation(() => Promise.resolve(testResp));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should run instant query and range query when in metrics mode', async () => {
|
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 () => {
|
test('should return series data', async () => {
|
||||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||||
const ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
const ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||||
backendSrvMock.datasourceRequest = jest
|
datasourceRequestMock.mockImplementation(
|
||||||
.fn()
|
jest
|
||||||
.mockReturnValueOnce(Promise.resolve(legacyTestResp))
|
.fn()
|
||||||
.mockReturnValueOnce(Promise.resolve(omit(legacyTestResp, 'status')));
|
.mockReturnValueOnce(Promise.resolve(legacyTestResp))
|
||||||
|
.mockReturnValueOnce(Promise.resolve(omit(legacyTestResp, 'status')))
|
||||||
|
);
|
||||||
|
|
||||||
const options = getQueryOptions<LokiQuery>({
|
const options = getQueryOptions<LokiQuery>({
|
||||||
targets: [{ expr: '{job="grafana"} |= "foo"', refId: 'B' }],
|
targets: [{ expr: '{job="grafana"} |= "foo"', refId: 'B' }],
|
||||||
@@ -209,7 +224,7 @@ describe('LokiDatasource', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
const customSettings = { ...instanceSettings, jsonData: customData };
|
||||||
ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
|
ds = new LokiDatasource(customSettings, templateSrvMock);
|
||||||
variable = new CustomVariable({}, {} as any);
|
variable = new CustomVariable({}, {} as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -256,17 +271,15 @@ describe('LokiDatasource', () => {
|
|||||||
|
|
||||||
describe('and call succeeds', () => {
|
describe('and call succeeds', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const backendSrv = ({
|
datasourceRequestMock.mockImplementation(async () => {
|
||||||
async datasourceRequest() {
|
return Promise.resolve({
|
||||||
return Promise.resolve({
|
status: 200,
|
||||||
status: 200,
|
data: {
|
||||||
data: {
|
values: ['avalue'],
|
||||||
data: ['avalue'],
|
},
|
||||||
},
|
});
|
||||||
});
|
});
|
||||||
},
|
ds = new LokiDatasource(instanceSettings, {} as TemplateSrv);
|
||||||
} as unknown) as BackendSrv;
|
|
||||||
ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
|
|
||||||
result = await ds.testDatasource();
|
result = await ds.testDatasource();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -278,7 +291,7 @@ describe('LokiDatasource', () => {
|
|||||||
describe('and call fails with 401 error', () => {
|
describe('and call fails with 401 error', () => {
|
||||||
let ds: LokiDatasource;
|
let ds: LokiDatasource;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrvMock.datasourceRequest = jest.fn(() =>
|
datasourceRequestMock.mockImplementation(() =>
|
||||||
Promise.reject({
|
Promise.reject({
|
||||||
statusText: 'Unauthorized',
|
statusText: 'Unauthorized',
|
||||||
status: 401,
|
status: 401,
|
||||||
@@ -290,7 +303,7 @@ describe('LokiDatasource', () => {
|
|||||||
|
|
||||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||||
const customSettings = { ...instanceSettings, jsonData: customData };
|
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 () => {
|
it('should return error status and a detailed error message', async () => {
|
||||||
@@ -302,16 +315,14 @@ describe('LokiDatasource', () => {
|
|||||||
|
|
||||||
describe('and call fails with 404 error', () => {
|
describe('and call fails with 404 error', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const backendSrv = ({
|
datasourceRequestMock.mockImplementation(() =>
|
||||||
async datasourceRequest() {
|
Promise.reject({
|
||||||
return Promise.reject({
|
statusText: 'Not found',
|
||||||
statusText: 'Not found',
|
status: 404,
|
||||||
status: 404,
|
data: '404 page not found',
|
||||||
data: '404 page not found',
|
})
|
||||||
});
|
);
|
||||||
},
|
ds = new LokiDatasource(instanceSettings, {} as TemplateSrv);
|
||||||
} as unknown) as BackendSrv;
|
|
||||||
ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
|
|
||||||
result = await ds.testDatasource();
|
result = await ds.testDatasource();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -323,16 +334,14 @@ describe('LokiDatasource', () => {
|
|||||||
|
|
||||||
describe('and call fails with 502 error', () => {
|
describe('and call fails with 502 error', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const backendSrv = ({
|
datasourceRequestMock.mockImplementation(() =>
|
||||||
async datasourceRequest() {
|
Promise.reject({
|
||||||
return Promise.reject({
|
statusText: 'Bad Gateway',
|
||||||
statusText: 'Bad Gateway',
|
status: 502,
|
||||||
status: 502,
|
data: '',
|
||||||
data: '',
|
})
|
||||||
});
|
);
|
||||||
},
|
ds = new LokiDatasource(instanceSettings, {} as TemplateSrv);
|
||||||
} as unknown) as BackendSrv;
|
|
||||||
ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
|
|
||||||
result = await ds.testDatasource();
|
result = await ds.testDatasource();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -344,7 +353,7 @@ describe('LokiDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('when creating a range query', () => {
|
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' };
|
const query: LokiQuery = { expr: 'foo', refId: 'bar' };
|
||||||
|
|
||||||
// Loki v1 API has an issue with float step parameters, can be removed when API is fixed
|
// 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', () => {
|
describe('annotationQuery', () => {
|
||||||
it('should transform the loki data to annotation response', async () => {
|
it('should transform the loki data to annotation response', async () => {
|
||||||
const ds = new LokiDatasource(instanceSettings, backendSrv, templateSrvMock);
|
const ds = new LokiDatasource(instanceSettings, templateSrvMock);
|
||||||
backendSrvMock.datasourceRequest = jest
|
datasourceRequestMock.mockImplementation(
|
||||||
.fn()
|
jest
|
||||||
.mockReturnValueOnce(
|
.fn()
|
||||||
Promise.resolve({
|
.mockReturnValueOnce(
|
||||||
data: [],
|
Promise.resolve({
|
||||||
status: 404,
|
data: [],
|
||||||
})
|
status: 404,
|
||||||
)
|
})
|
||||||
.mockReturnValueOnce(
|
)
|
||||||
Promise.resolve({
|
.mockReturnValueOnce(
|
||||||
data: {
|
Promise.resolve({
|
||||||
streams: [
|
data: {
|
||||||
{
|
streams: [
|
||||||
entries: [{ ts: '2019-02-01T10:27:37.498180581Z', line: 'hello' }],
|
{
|
||||||
labels: '{label="value"}',
|
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"}',
|
entries: [{ ts: '2019-02-01T12:27:37.498180581Z', line: 'hello 2' }],
|
||||||
},
|
labels: '{label2="value2"}',
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
})
|
},
|
||||||
);
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
const query = makeAnnotationQueryRequest();
|
const query = makeAnnotationQueryRequest();
|
||||||
|
|
||||||
const res = await ds.annotationQuery(query);
|
const res = await ds.annotationQuery(query);
|
||||||
@@ -400,7 +411,7 @@ describe('LokiDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('metricFindQuery', () => {
|
describe('metricFindQuery', () => {
|
||||||
const ds = new LokiDatasource(instanceSettings, backendSrv, templateSrvMock);
|
const ds = new LokiDatasource(instanceSettings, templateSrvMock);
|
||||||
const mocks = makeMetadataAndVersionsMocks();
|
const mocks = makeMetadataAndVersionsMocks();
|
||||||
|
|
||||||
mocks.forEach((mock, index) => {
|
mocks.forEach((mock, index) => {
|
||||||
@@ -456,21 +467,15 @@ type LimitTestArgs = {
|
|||||||
maxLines?: number;
|
maxLines?: number;
|
||||||
expectedLimit: number;
|
expectedLimit: number;
|
||||||
};
|
};
|
||||||
function makeLimitTest(
|
function makeLimitTest(instanceSettings: any, datasourceRequestMock: any, templateSrvMock: any, testResp: any) {
|
||||||
instanceSettings: any,
|
|
||||||
backendSrvMock: any,
|
|
||||||
backendSrv: any,
|
|
||||||
templateSrvMock: any,
|
|
||||||
testResp: any
|
|
||||||
) {
|
|
||||||
return ({ maxDataPoints, maxLines, expectedLimit }: LimitTestArgs) => {
|
return ({ maxDataPoints, maxLines, expectedLimit }: LimitTestArgs) => {
|
||||||
let settings = instanceSettings;
|
let settings = instanceSettings;
|
||||||
if (Number.isFinite(maxLines)) {
|
if (Number.isFinite(maxLines)) {
|
||||||
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
|
||||||
settings = { ...instanceSettings, jsonData: customData };
|
settings = { ...instanceSettings, jsonData: customData };
|
||||||
}
|
}
|
||||||
const ds = new LokiDatasource(settings, backendSrv, templateSrvMock);
|
const ds = new LokiDatasource(settings, templateSrvMock);
|
||||||
backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
|
datasourceRequestMock.mockImplementation(() => Promise.resolve(testResp));
|
||||||
|
|
||||||
const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
|
const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
|
||||||
if (Number.isFinite(maxDataPoints)) {
|
if (Number.isFinite(maxDataPoints)) {
|
||||||
@@ -482,8 +487,8 @@ function makeLimitTest(
|
|||||||
|
|
||||||
ds.query(options);
|
ds.query(options);
|
||||||
|
|
||||||
expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
|
expect(datasourceRequestMock.mock.calls.length).toBe(1);
|
||||||
expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain(`limit=${expectedLimit}`);
|
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
|
// Services & Utils
|
||||||
import { dateMath } from '@grafana/data';
|
import { dateMath } from '@grafana/data';
|
||||||
import { addLabelToSelector, keepSelectorFilters } from 'app/plugins/datasource/prometheus/add_label_to_query';
|
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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { safeStringifyValue, convertToWebSocketUrl } from 'app/core/utils/explore';
|
import { safeStringifyValue, convertToWebSocketUrl } from 'app/core/utils/explore';
|
||||||
import {
|
import {
|
||||||
@@ -84,11 +85,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
maxLines: number;
|
maxLines: number;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(private instanceSettings: DataSourceInstanceSettings<LokiOptions>, private templateSrv: TemplateSrv) {
|
||||||
private instanceSettings: DataSourceInstanceSettings<LokiOptions>,
|
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv
|
|
||||||
) {
|
|
||||||
super(instanceSettings);
|
super(instanceSettings);
|
||||||
|
|
||||||
this.languageProvider = new LanguageProvider(this);
|
this.languageProvider = new LanguageProvider(this);
|
||||||
@@ -122,7 +119,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
url,
|
url,
|
||||||
};
|
};
|
||||||
|
|
||||||
return from(this.backendSrv.datasourceRequest(req));
|
return from(getBackendSrv().datasourceRequest(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
query(options: DataQueryRequest<LokiQuery>): Observable<DataQueryResponse> {
|
query(options: DataQueryRequest<LokiQuery>): Observable<DataQueryResponse> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ResponseParser from './response_parser';
|
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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
//Types
|
//Types
|
||||||
@@ -13,12 +13,7 @@ export class MssqlDatasource {
|
|||||||
interval: string;
|
interval: string;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(instanceSettings: any, private templateSrv: TemplateSrv, private timeSrv: TimeSrv) {
|
||||||
instanceSettings: any,
|
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv,
|
|
||||||
private timeSrv: TimeSrv
|
|
||||||
) {
|
|
||||||
this.name = instanceSettings.name;
|
this.name = instanceSettings.name;
|
||||||
this.id = instanceSettings.id;
|
this.id = instanceSettings.id;
|
||||||
this.responseParser = new ResponseParser();
|
this.responseParser = new ResponseParser();
|
||||||
@@ -81,7 +76,7 @@ export class MssqlDatasource {
|
|||||||
return Promise.resolve({ data: [] });
|
return Promise.resolve({ data: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -106,7 +101,7 @@ export class MssqlDatasource {
|
|||||||
format: 'table',
|
format: 'table',
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -139,7 +134,7 @@ export class MssqlDatasource {
|
|||||||
to: range.to.valueOf().toString(),
|
to: range.to.valueOf().toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -149,7 +144,7 @@ export class MssqlDatasource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testDatasource() {
|
testDatasource() {
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -4,19 +4,26 @@ import { CustomVariable } from 'app/features/templating/custom_variable';
|
|||||||
|
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
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', () => {
|
describe('MSSQLDatasource', () => {
|
||||||
const templateSrv: TemplateSrv = new TemplateSrv();
|
const templateSrv: TemplateSrv = new TemplateSrv();
|
||||||
|
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
|
||||||
|
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
backendSrv: {},
|
|
||||||
timeSrv: new TimeSrvStub(),
|
timeSrv: new TimeSrvStub(),
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
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', () => {
|
describe('When performing annotationQuery', () => {
|
||||||
@@ -54,9 +61,7 @@ describe('MSSQLDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 }));
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
|
||||||
};
|
|
||||||
|
|
||||||
return ctx.ds.annotationQuery(options).then((data: any) => {
|
return ctx.ds.annotationQuery(options).then((data: any) => {
|
||||||
results = data;
|
results = data;
|
||||||
@@ -102,9 +107,7 @@ describe('MSSQLDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 }));
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
|
||||||
};
|
|
||||||
|
|
||||||
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
||||||
results = data;
|
results = data;
|
||||||
@@ -143,9 +146,7 @@ describe('MSSQLDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 }));
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
|
||||||
};
|
|
||||||
|
|
||||||
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
||||||
results = data;
|
results = data;
|
||||||
@@ -186,10 +187,7 @@ describe('MSSQLDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => Promise.resolve({ data: response, status: 200 }));
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
|
||||||
};
|
|
||||||
|
|
||||||
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
return ctx.ds.metricFindQuery(query).then((data: any) => {
|
||||||
results = data;
|
results = data;
|
||||||
});
|
});
|
||||||
@@ -229,10 +227,10 @@ describe('MSSQLDatasource', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.timeSrv.setTime(time);
|
ctx.timeSrv.setTime(time);
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = (options: any) => {
|
datasourceRequestMock.mockImplementation((options: any) => {
|
||||||
results = options.data;
|
results = options.data;
|
||||||
return Promise.resolve({ data: response, status: 200 });
|
return Promise.resolve({ data: response, status: 200 });
|
||||||
};
|
});
|
||||||
|
|
||||||
return ctx.ds.metricFindQuery(query);
|
return ctx.ds.metricFindQuery(query);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
import MysqlQuery from 'app/plugins/datasource/mysql/mysql_query';
|
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 { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
//Types
|
//Types
|
||||||
@@ -16,12 +16,7 @@ export class MysqlDatasource {
|
|||||||
interval: string;
|
interval: string;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(instanceSettings: any, private templateSrv: TemplateSrv, private timeSrv: TimeSrv) {
|
||||||
instanceSettings: any,
|
|
||||||
private backendSrv: BackendSrv,
|
|
||||||
private templateSrv: TemplateSrv,
|
|
||||||
private timeSrv: TimeSrv
|
|
||||||
) {
|
|
||||||
this.name = instanceSettings.name;
|
this.name = instanceSettings.name;
|
||||||
this.id = instanceSettings.id;
|
this.id = instanceSettings.id;
|
||||||
this.responseParser = new ResponseParser();
|
this.responseParser = new ResponseParser();
|
||||||
@@ -83,7 +78,7 @@ export class MysqlDatasource {
|
|||||||
return Promise.resolve({ data: [] });
|
return Promise.resolve({ data: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -110,7 +105,7 @@ export class MysqlDatasource {
|
|||||||
format: 'table',
|
format: 'table',
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -156,7 +151,7 @@ export class MysqlDatasource {
|
|||||||
data['to'] = optionalOptions.range.to.valueOf().toString();
|
data['to'] = optionalOptions.range.to.valueOf().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -166,7 +161,7 @@ export class MysqlDatasource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testDatasource() {
|
testDatasource() {
|
||||||
return this.backendSrv
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: '/api/tsdb/query',
|
url: '/api/tsdb/query',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user