mirror of https://github.com/grafana/grafana
PublicDashboards: Data discrepancy fix. Use real datasource plugin when it is a public dashboard. (#73708)
Co-authored-by: Torkel Ödegaard <torkel@grafana.com> Co-authored-by: Ezequiel Victorero <ezequiel.victorero@grafana.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com>pull/74110/head
parent
5038137662
commit
969ef5282c
@ -0,0 +1,50 @@ |
|||||||
|
import { catchError, Observable, of, switchMap } from 'rxjs'; |
||||||
|
|
||||||
|
import { DataQuery, DataQueryRequest, DataQueryResponse } from '@grafana/data'; |
||||||
|
|
||||||
|
import { config } from '../config'; |
||||||
|
import { getBackendSrv } from '../services/backendSrv'; |
||||||
|
|
||||||
|
import { BackendDataSourceResponse, toDataQueryResponse } from './queryResponse'; |
||||||
|
|
||||||
|
export function publicDashboardQueryHandler(request: DataQueryRequest<DataQuery>): Observable<DataQueryResponse> { |
||||||
|
const { |
||||||
|
intervalMs, |
||||||
|
maxDataPoints, |
||||||
|
requestId, |
||||||
|
panelId, |
||||||
|
queryCachingTTL, |
||||||
|
range: { from: fromRange, to: toRange }, |
||||||
|
} = request; |
||||||
|
// Return early if no queries exist
|
||||||
|
if (!request.targets.length) { |
||||||
|
return of({ data: [] }); |
||||||
|
} |
||||||
|
|
||||||
|
const body = { |
||||||
|
intervalMs, |
||||||
|
maxDataPoints, |
||||||
|
queryCachingTTL, |
||||||
|
timeRange: { |
||||||
|
from: fromRange.valueOf().toString(), |
||||||
|
to: toRange.valueOf().toString(), |
||||||
|
timezone: request.timezone, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
return getBackendSrv() |
||||||
|
.fetch<BackendDataSourceResponse>({ |
||||||
|
url: `/api/public/dashboards/${config.publicDashboardAccessToken!}/panels/${panelId}/query`, |
||||||
|
method: 'POST', |
||||||
|
data: body, |
||||||
|
requestId, |
||||||
|
}) |
||||||
|
.pipe( |
||||||
|
switchMap((raw) => { |
||||||
|
return of(toDataQueryResponse(raw, request.targets)); |
||||||
|
}), |
||||||
|
catchError((err) => { |
||||||
|
return of(toDataQueryResponse(err)); |
||||||
|
}) |
||||||
|
); |
||||||
|
} |
@ -1,181 +0,0 @@ |
|||||||
import { of } from 'rxjs'; |
|
||||||
|
|
||||||
import { DataQueryRequest, DataSourceInstanceSettings, DataSourceRef, dateTime, TimeRange } from '@grafana/data'; |
|
||||||
import { BackendSrvRequest, BackendSrv, DataSourceWithBackend, config } from '@grafana/runtime'; |
|
||||||
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; |
|
||||||
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; |
|
||||||
|
|
||||||
import { GRAFANA_DATASOURCE_NAME } from '../../alerting/unified/utils/datasource'; |
|
||||||
|
|
||||||
import { PublicDashboardDataSource, PUBLIC_DATASOURCE, DEFAULT_INTERVAL } from './PublicDashboardDataSource'; |
|
||||||
|
|
||||||
const mockDatasourceRequest = jest.fn(); |
|
||||||
|
|
||||||
const backendSrv = { |
|
||||||
fetch: (options: BackendSrvRequest) => { |
|
||||||
return of(mockDatasourceRequest(options)); |
|
||||||
}, |
|
||||||
get: (url: string, options?: Partial<BackendSrvRequest>) => { |
|
||||||
return mockDatasourceRequest(url, options); |
|
||||||
}, |
|
||||||
} as unknown as BackendSrv; |
|
||||||
|
|
||||||
jest.mock('@grafana/runtime', () => ({ |
|
||||||
...jest.requireActual('@grafana/runtime'), |
|
||||||
getBackendSrv: () => backendSrv, |
|
||||||
getDataSourceSrv: () => { |
|
||||||
return { |
|
||||||
getInstanceSettings: (ref?: DataSourceRef) => ({ type: ref?.type ?? '?', uid: ref?.uid ?? '?' }), |
|
||||||
}; |
|
||||||
}, |
|
||||||
})); |
|
||||||
|
|
||||||
describe('PublicDashboardDatasource', () => { |
|
||||||
test('will add annotation query type to annotations', () => { |
|
||||||
const ds = new PublicDashboardDataSource('public'); |
|
||||||
const annotationQuery = { |
|
||||||
enable: true, |
|
||||||
name: 'someName', |
|
||||||
iconColor: 'red', |
|
||||||
}; |
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const annotation = ds?.annotations.prepareQuery(annotationQuery); |
|
||||||
|
|
||||||
expect(annotation?.queryType).toEqual(GrafanaQueryType.Annotations); |
|
||||||
}); |
|
||||||
|
|
||||||
test('fetches results from the pubdash annotations endpoint when it is an annotation query', async () => { |
|
||||||
mockDatasourceRequest.mockReset(); |
|
||||||
mockDatasourceRequest.mockReturnValue(Promise.resolve([])); |
|
||||||
|
|
||||||
const ds = new PublicDashboardDataSource('public'); |
|
||||||
const panelId = 1; |
|
||||||
|
|
||||||
config.publicDashboardAccessToken = 'abc123'; |
|
||||||
|
|
||||||
await ds.query({ |
|
||||||
maxDataPoints: 10, |
|
||||||
intervalMs: 5000, |
|
||||||
targets: [ |
|
||||||
{ |
|
||||||
refId: 'A', |
|
||||||
datasource: { uid: GRAFANA_DATASOURCE_NAME, type: 'sample' }, |
|
||||||
queryType: GrafanaQueryType.Annotations, |
|
||||||
}, |
|
||||||
], |
|
||||||
panelId, |
|
||||||
range: { from: new Date().toLocaleString(), to: new Date().toLocaleString() } as unknown as TimeRange, |
|
||||||
} as DataQueryRequest); |
|
||||||
|
|
||||||
const mock = mockDatasourceRequest.mock; |
|
||||||
|
|
||||||
expect(mock.calls.length).toBe(1); |
|
||||||
expect(mock.lastCall[0]).toEqual(`/api/public/dashboards/abc123/annotations`); |
|
||||||
}); |
|
||||||
|
|
||||||
test('fetches results from the pubdash query endpoint when not annotation query', () => { |
|
||||||
mockDatasourceRequest.mockReset(); |
|
||||||
mockDatasourceRequest.mockReturnValue(Promise.resolve({})); |
|
||||||
|
|
||||||
const ds = new PublicDashboardDataSource('public'); |
|
||||||
const panelId = 1; |
|
||||||
config.publicDashboardAccessToken = 'abc123'; |
|
||||||
|
|
||||||
ds.query({ |
|
||||||
maxDataPoints: 10, |
|
||||||
intervalMs: 5000, |
|
||||||
targets: [{ refId: 'A' }, { refId: 'B', datasource: { type: 'sample' } }], |
|
||||||
panelId, |
|
||||||
range: { |
|
||||||
from: dateTime('2022-01-01T15:55:00Z'), |
|
||||||
to: dateTime('2022-07-12T15:55:00Z'), |
|
||||||
raw: { |
|
||||||
from: 'now-15m', |
|
||||||
to: 'now', |
|
||||||
}, |
|
||||||
}, |
|
||||||
} as DataQueryRequest); |
|
||||||
|
|
||||||
const mock = mockDatasourceRequest.mock; |
|
||||||
|
|
||||||
expect(mock.calls.length).toBe(1); |
|
||||||
expect(mock.lastCall[0].url).toEqual(`/api/public/dashboards/abc123/panels/${panelId}/query`); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns public datasource uid when datasource passed in is null', () => { |
|
||||||
let ds = new PublicDashboardDataSource(null); |
|
||||||
expect(ds.uid).toBe(PUBLIC_DATASOURCE); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns datasource when datasource passed in is a string', () => { |
|
||||||
let ds = new PublicDashboardDataSource('theDatasourceUid'); |
|
||||||
expect(ds.uid).toBe('theDatasourceUid'); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns datasource uid when datasource passed in is a DataSourceRef implementation', () => { |
|
||||||
const datasource = { type: 'datasource', uid: 'abc123' }; |
|
||||||
let ds = new PublicDashboardDataSource(datasource); |
|
||||||
expect(ds.uid).toBe('abc123'); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns datasource uid when datasource passed in is a DatasourceApi instance', () => { |
|
||||||
const settings: DataSourceInstanceSettings = { id: 1, uid: 'abc123' } as DataSourceInstanceSettings; |
|
||||||
const datasource = new DataSourceWithBackend(settings); |
|
||||||
let ds = new PublicDashboardDataSource(datasource); |
|
||||||
expect(ds.uid).toBe('abc123'); |
|
||||||
}); |
|
||||||
|
|
||||||
test('isMixedDatasource returns true when datasource is mixed', () => { |
|
||||||
const datasource = new DataSourceWithBackend({ id: 1, uid: MIXED_DATASOURCE_NAME } as DataSourceInstanceSettings); |
|
||||||
let ds = new PublicDashboardDataSource(datasource); |
|
||||||
expect(ds.meta.mixed).toBeTruthy(); |
|
||||||
}); |
|
||||||
|
|
||||||
test('isMixedDatasource returns false when datasource is not mixed', () => { |
|
||||||
const datasource = new DataSourceWithBackend({ id: 1, uid: 'abc123' } as DataSourceInstanceSettings); |
|
||||||
let ds = new PublicDashboardDataSource(datasource); |
|
||||||
expect(ds.meta.mixed).toBeFalsy(); |
|
||||||
}); |
|
||||||
|
|
||||||
test('isMixedDatasource returns false when datasource is a string', () => { |
|
||||||
let ds = new PublicDashboardDataSource('abc123'); |
|
||||||
expect(ds.meta.mixed).toBeFalsy(); |
|
||||||
}); |
|
||||||
|
|
||||||
test('isMixedDatasource returns false when datasource is null', () => { |
|
||||||
let ds = new PublicDashboardDataSource(null); |
|
||||||
expect(ds.meta.mixed).toBeFalsy(); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns default datasource interval when datasource passed in is null', () => { |
|
||||||
let ds = new PublicDashboardDataSource(null); |
|
||||||
expect(ds.interval).toBe(DEFAULT_INTERVAL); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns default datasource interval when datasource passed in is a string', () => { |
|
||||||
let ds = new PublicDashboardDataSource('theDatasourceUid'); |
|
||||||
expect(ds.interval).toBe(DEFAULT_INTERVAL); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns default datasource interval when datasource passed in is a DataSourceRef implementation', () => { |
|
||||||
const datasource = { type: 'datasource', uid: 'abc123' }; |
|
||||||
let ds = new PublicDashboardDataSource(datasource); |
|
||||||
expect(ds.interval).toBe(DEFAULT_INTERVAL); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns default datasource interval when datasource passed in is a DatasourceApi instance that has no interval', () => { |
|
||||||
const settings: DataSourceInstanceSettings = { id: 1, uid: 'abc123' } as DataSourceInstanceSettings; |
|
||||||
const datasource = new DataSourceWithBackend(settings); |
|
||||||
let ds = new PublicDashboardDataSource(datasource); |
|
||||||
expect(ds.interval).toBe(DEFAULT_INTERVAL); |
|
||||||
}); |
|
||||||
|
|
||||||
test('returns datasource interval when datasource passed in is a DatasourceApi instance that has interval', () => { |
|
||||||
const settings: DataSourceInstanceSettings = { id: 1, uid: 'abc123' } as DataSourceInstanceSettings; |
|
||||||
const datasource = new DataSourceWithBackend(settings); |
|
||||||
datasource.interval = 'abc123'; |
|
||||||
let ds = new PublicDashboardDataSource(datasource); |
|
||||||
expect(ds.interval).toBe('abc123'); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,164 +0,0 @@ |
|||||||
import { catchError, from, Observable, of, switchMap } from 'rxjs'; |
|
||||||
|
|
||||||
import { |
|
||||||
AnnotationQuery, |
|
||||||
DataQuery, |
|
||||||
DataQueryRequest, |
|
||||||
DataQueryResponse, |
|
||||||
TestDataSourceResponse, |
|
||||||
DataSourceApi, |
|
||||||
DataSourceJsonData, |
|
||||||
DataSourcePluginMeta, |
|
||||||
DataSourceRef, |
|
||||||
toDataFrame, |
|
||||||
} from '@grafana/data'; |
|
||||||
import { BackendDataSourceResponse, config, getBackendSrv, toDataQueryResponse } from '@grafana/runtime'; |
|
||||||
|
|
||||||
import { GrafanaQueryType } from '../../../plugins/datasource/grafana/types'; |
|
||||||
import { MIXED_DATASOURCE_NAME } from '../../../plugins/datasource/mixed/MixedDataSource'; |
|
||||||
import { GRAFANA_DATASOURCE_NAME } from '../../alerting/unified/utils/datasource'; |
|
||||||
|
|
||||||
export const PUBLIC_DATASOURCE = '-- Public --'; |
|
||||||
export const DEFAULT_INTERVAL = '1min'; |
|
||||||
|
|
||||||
export class PublicDashboardDataSource extends DataSourceApi<DataQuery, DataSourceJsonData, {}> { |
|
||||||
constructor(datasource: DataSourceRef | string | DataSourceApi | null) { |
|
||||||
let meta = {} as DataSourcePluginMeta; |
|
||||||
if (PublicDashboardDataSource.isMixedDatasource(datasource)) { |
|
||||||
meta.mixed = true; |
|
||||||
} |
|
||||||
|
|
||||||
super({ |
|
||||||
name: 'public-ds', |
|
||||||
id: 0, |
|
||||||
type: 'public-ds', |
|
||||||
meta, |
|
||||||
uid: PublicDashboardDataSource.resolveUid(datasource), |
|
||||||
jsonData: {}, |
|
||||||
access: 'proxy', |
|
||||||
readOnly: true, |
|
||||||
}); |
|
||||||
|
|
||||||
this.interval = PublicDashboardDataSource.resolveInterval(datasource); |
|
||||||
|
|
||||||
this.annotations = { |
|
||||||
prepareQuery(anno: AnnotationQuery): DataQuery | undefined { |
|
||||||
return { ...anno, queryType: GrafanaQueryType.Annotations, refId: 'anno' }; |
|
||||||
}, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Get the datasource uid based on the many types a datasource can be. |
|
||||||
*/ |
|
||||||
private static resolveUid(datasource: DataSourceRef | string | DataSourceApi | null): string { |
|
||||||
if (typeof datasource === 'string') { |
|
||||||
return datasource; |
|
||||||
} |
|
||||||
|
|
||||||
return datasource?.uid ?? PUBLIC_DATASOURCE; |
|
||||||
} |
|
||||||
|
|
||||||
private static isMixedDatasource(datasource: DataSourceRef | string | DataSourceApi | null): boolean { |
|
||||||
if (typeof datasource === 'string' || datasource == null) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
return datasource?.uid === MIXED_DATASOURCE_NAME; |
|
||||||
} |
|
||||||
|
|
||||||
private static resolveInterval(datasource: DataSourceRef | string | DataSourceApi | null): string { |
|
||||||
if (typeof datasource === 'string' || datasource == null) { |
|
||||||
return DEFAULT_INTERVAL; |
|
||||||
} |
|
||||||
|
|
||||||
const interval = 'interval' in datasource ? datasource.interval : undefined; |
|
||||||
|
|
||||||
return interval ?? DEFAULT_INTERVAL; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Ideally final -- any other implementation may not work as expected |
|
||||||
*/ |
|
||||||
query(request: DataQueryRequest<DataQuery>): Observable<DataQueryResponse> { |
|
||||||
const { |
|
||||||
intervalMs, |
|
||||||
maxDataPoints, |
|
||||||
requestId, |
|
||||||
panelId, |
|
||||||
queryCachingTTL, |
|
||||||
range: { from: fromRange, to: toRange }, |
|
||||||
} = request; |
|
||||||
// Return early if no queries exist
|
|
||||||
if (!request.targets.length) { |
|
||||||
return of({ data: [] }); |
|
||||||
} |
|
||||||
|
|
||||||
// Its an annotations query
|
|
||||||
// Currently, annotations requests come in one at a time, so there will only be one target
|
|
||||||
const target = request.targets[0]; |
|
||||||
if (target.queryType === GrafanaQueryType.Annotations) { |
|
||||||
if (target?.datasource?.uid === GRAFANA_DATASOURCE_NAME) { |
|
||||||
return from(this.getAnnotations(request)); |
|
||||||
} |
|
||||||
return of({ data: [] }); |
|
||||||
} |
|
||||||
|
|
||||||
// Its a datasource query
|
|
||||||
else { |
|
||||||
const body = { |
|
||||||
intervalMs, |
|
||||||
maxDataPoints, |
|
||||||
queryCachingTTL, |
|
||||||
timeRange: { |
|
||||||
from: fromRange.valueOf().toString(), |
|
||||||
to: toRange.valueOf().toString(), |
|
||||||
timezone: this.getBrowserTimezone(), |
|
||||||
}, |
|
||||||
}; |
|
||||||
|
|
||||||
return getBackendSrv() |
|
||||||
.fetch<BackendDataSourceResponse>({ |
|
||||||
url: `/api/public/dashboards/${config.publicDashboardAccessToken!}/panels/${panelId}/query`, |
|
||||||
method: 'POST', |
|
||||||
data: body, |
|
||||||
requestId, |
|
||||||
}) |
|
||||||
.pipe( |
|
||||||
switchMap((raw) => { |
|
||||||
return of(toDataQueryResponse(raw, request.targets)); |
|
||||||
}), |
|
||||||
catchError((err) => { |
|
||||||
return of(toDataQueryResponse(err)); |
|
||||||
}) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async getAnnotations(request: DataQueryRequest<DataQuery>): Promise<DataQueryResponse> { |
|
||||||
const { |
|
||||||
range: { to, from }, |
|
||||||
} = request; |
|
||||||
|
|
||||||
const params = { |
|
||||||
from: from.valueOf(), |
|
||||||
to: to.valueOf(), |
|
||||||
}; |
|
||||||
|
|
||||||
const annotations = await getBackendSrv().get( |
|
||||||
`/api/public/dashboards/${config.publicDashboardAccessToken!}/annotations`, |
|
||||||
params |
|
||||||
); |
|
||||||
|
|
||||||
return { data: [toDataFrame(annotations)] }; |
|
||||||
} |
|
||||||
|
|
||||||
testDatasource(): Promise<TestDataSourceResponse> { |
|
||||||
return Promise.resolve({ message: '', status: '' }); |
|
||||||
} |
|
||||||
|
|
||||||
// Try to get the browser timezone otherwise return blank
|
|
||||||
getBrowserTimezone(): string { |
|
||||||
return window.Intl?.DateTimeFormat().resolvedOptions()?.timeZone || ''; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,74 @@ |
|||||||
|
import { of } from 'rxjs'; |
||||||
|
|
||||||
|
import { DataQueryRequest, DataSourceRef, TimeRange } from '@grafana/data'; |
||||||
|
import { BackendSrvRequest, BackendSrv, config } from '@grafana/runtime'; |
||||||
|
import { GRAFANA_DATASOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; |
||||||
|
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; |
||||||
|
|
||||||
|
import { PublicAnnotationsDataSource } from './PublicAnnotationsDataSource'; |
||||||
|
|
||||||
|
const mockDatasourceRequest = jest.fn(); |
||||||
|
|
||||||
|
const backendSrv = { |
||||||
|
fetch: (options: BackendSrvRequest) => { |
||||||
|
return of(mockDatasourceRequest(options)); |
||||||
|
}, |
||||||
|
get: (url: string, options?: Partial<BackendSrvRequest>) => { |
||||||
|
return mockDatasourceRequest(url, options); |
||||||
|
}, |
||||||
|
} as unknown as BackendSrv; |
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({ |
||||||
|
...jest.requireActual('@grafana/runtime'), |
||||||
|
getBackendSrv: () => backendSrv, |
||||||
|
getDataSourceSrv: () => { |
||||||
|
return { |
||||||
|
getInstanceSettings: (ref?: DataSourceRef) => ({ type: ref?.type ?? '?', uid: ref?.uid ?? '?' }), |
||||||
|
}; |
||||||
|
}, |
||||||
|
})); |
||||||
|
|
||||||
|
describe('PublicDashboardDatasource', () => { |
||||||
|
test('will add annotation query type to annotations', () => { |
||||||
|
const ds = new PublicAnnotationsDataSource(); |
||||||
|
const annotationQuery = { |
||||||
|
enable: true, |
||||||
|
name: 'someName', |
||||||
|
iconColor: 'red', |
||||||
|
}; |
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const annotation = ds?.annotations.prepareQuery(annotationQuery); |
||||||
|
|
||||||
|
expect(annotation?.queryType).toEqual(GrafanaQueryType.Annotations); |
||||||
|
}); |
||||||
|
|
||||||
|
test('fetches results from the pubdash annotations endpoint when it is an annotation query', async () => { |
||||||
|
mockDatasourceRequest.mockReset(); |
||||||
|
mockDatasourceRequest.mockReturnValue(Promise.resolve([])); |
||||||
|
|
||||||
|
const ds = new PublicAnnotationsDataSource(); |
||||||
|
const panelId = 1; |
||||||
|
|
||||||
|
config.publicDashboardAccessToken = 'abc123'; |
||||||
|
|
||||||
|
await ds.query({ |
||||||
|
maxDataPoints: 10, |
||||||
|
intervalMs: 5000, |
||||||
|
targets: [ |
||||||
|
{ |
||||||
|
refId: 'A', |
||||||
|
datasource: { uid: GRAFANA_DATASOURCE_NAME, type: 'sample' }, |
||||||
|
queryType: GrafanaQueryType.Annotations, |
||||||
|
}, |
||||||
|
], |
||||||
|
panelId, |
||||||
|
range: { from: new Date().toLocaleString(), to: new Date().toLocaleString() } as unknown as TimeRange, |
||||||
|
} as DataQueryRequest); |
||||||
|
|
||||||
|
const mock = mockDatasourceRequest.mock; |
||||||
|
|
||||||
|
expect(mock.calls.length).toBe(1); |
||||||
|
expect(mock.lastCall[0]).toEqual(`/api/public/dashboards/abc123/annotations`); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,82 @@ |
|||||||
|
import { from, Observable, of } from 'rxjs'; |
||||||
|
|
||||||
|
import { |
||||||
|
AnnotationQuery, |
||||||
|
DataQuery, |
||||||
|
DataQueryRequest, |
||||||
|
DataQueryResponse, |
||||||
|
TestDataSourceResponse, |
||||||
|
DataSourceApi, |
||||||
|
DataSourceJsonData, |
||||||
|
DataSourcePluginMeta, |
||||||
|
toDataFrame, |
||||||
|
} from '@grafana/data'; |
||||||
|
import { config, getBackendSrv } from '@grafana/runtime'; |
||||||
|
import { GRAFANA_DATASOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; |
||||||
|
|
||||||
|
import { GrafanaQueryType } from '../../../../plugins/datasource/grafana/types'; |
||||||
|
|
||||||
|
export const PUBLIC_DATASOURCE = '-- Public --'; |
||||||
|
|
||||||
|
export class PublicAnnotationsDataSource extends DataSourceApi<DataQuery, DataSourceJsonData, {}> { |
||||||
|
constructor() { |
||||||
|
let meta = {} as DataSourcePluginMeta; |
||||||
|
|
||||||
|
super({ |
||||||
|
name: 'public-ds', |
||||||
|
id: 0, |
||||||
|
type: 'public-ds', |
||||||
|
meta, |
||||||
|
uid: PUBLIC_DATASOURCE, |
||||||
|
jsonData: {}, |
||||||
|
access: 'proxy', |
||||||
|
readOnly: true, |
||||||
|
}); |
||||||
|
|
||||||
|
this.annotations = { |
||||||
|
prepareQuery(anno: AnnotationQuery): DataQuery | undefined { |
||||||
|
return { ...anno, queryType: GrafanaQueryType.Annotations, refId: 'anno' }; |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Ideally final -- any other implementation may not work as expected |
||||||
|
*/ |
||||||
|
query(request: DataQueryRequest<DataQuery>): Observable<DataQueryResponse> { |
||||||
|
// Return early if no queries exist
|
||||||
|
if (!request.targets.length) { |
||||||
|
return of({ data: [] }); |
||||||
|
} |
||||||
|
|
||||||
|
// Currently, annotations requests come in one at a time, so there will only be one target
|
||||||
|
const target = request.targets[0]; |
||||||
|
|
||||||
|
if (target?.datasource?.uid === GRAFANA_DATASOURCE_NAME) { |
||||||
|
return from(this.getAnnotations(request)); |
||||||
|
} |
||||||
|
return of({ data: [] }); |
||||||
|
} |
||||||
|
|
||||||
|
async getAnnotations(request: DataQueryRequest<DataQuery>): Promise<DataQueryResponse> { |
||||||
|
const { |
||||||
|
range: { to, from }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
const params = { |
||||||
|
from: from.valueOf(), |
||||||
|
to: to.valueOf(), |
||||||
|
}; |
||||||
|
|
||||||
|
const annotations = await getBackendSrv().get( |
||||||
|
`/api/public/dashboards/${config.publicDashboardAccessToken}/annotations`, |
||||||
|
params |
||||||
|
); |
||||||
|
|
||||||
|
return { data: [toDataFrame(annotations)] }; |
||||||
|
} |
||||||
|
|
||||||
|
testDatasource(): Promise<TestDataSourceResponse> { |
||||||
|
return Promise.resolve({ message: '', status: '' }); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue