mirror of https://github.com/grafana/grafana
Refactor: split PanelQueryRunner into runner and state (#16685)
* check for running * split out panel state * adding test file * remove bad testpull/16688/head
parent
178ce8eec8
commit
e7f56a74fc
@ -0,0 +1,46 @@ |
||||
import { toDataQueryError, PanelQueryState } from './PanelQueryState'; |
||||
import { MockDataSourceApi } from 'test/mocks/datasource_srv'; |
||||
import { DataQueryResponse } from '@grafana/ui'; |
||||
import { getQueryOptions } from 'test/helpers/getQueryOptions'; |
||||
|
||||
describe('PanelQueryState', () => { |
||||
it('converts anythign to an error', () => { |
||||
let err = toDataQueryError(undefined); |
||||
expect(err.message).toEqual('Query error'); |
||||
|
||||
err = toDataQueryError('STRING ERRROR'); |
||||
expect(err.message).toEqual('STRING ERRROR'); |
||||
|
||||
err = toDataQueryError({ message: 'hello' }); |
||||
expect(err.message).toEqual('hello'); |
||||
}); |
||||
|
||||
it('keeps track of running queries', async () => { |
||||
const state = new PanelQueryState(); |
||||
expect(state.isRunning()).toBeFalsy(); |
||||
let hasRun = false; |
||||
const dsRunner = new Promise<DataQueryResponse>((resolve, reject) => { |
||||
// The status should be running when we get here
|
||||
expect(state.isRunning()).toBeTruthy(); |
||||
resolve({ data: ['x', 'y'] }); |
||||
hasRun = true; |
||||
}); |
||||
const ds = new MockDataSourceApi('test'); |
||||
ds.queryResolver = dsRunner; |
||||
|
||||
// should not actually run for an empty query
|
||||
let empty = await state.execute(ds, getQueryOptions({})); |
||||
expect(state.isRunning()).toBeFalsy(); |
||||
expect(empty.series.length).toBe(0); |
||||
expect(hasRun).toBeFalsy(); |
||||
|
||||
empty = await state.execute( |
||||
ds, |
||||
getQueryOptions({ targets: [{ hide: true, refId: 'X' }, { hide: true, refId: 'Y' }, { hide: true, refId: 'Z' }] }) |
||||
); |
||||
// should not run any hidden queries'
|
||||
expect(state.isRunning()).toBeFalsy(); |
||||
expect(empty.series.length).toBe(0); |
||||
expect(hasRun).toBeFalsy(); |
||||
}); |
||||
}); |
@ -0,0 +1,179 @@ |
||||
import { |
||||
DataSourceApi, |
||||
DataQueryRequest, |
||||
PanelData, |
||||
LoadingState, |
||||
toLegacyResponseData, |
||||
isSeriesData, |
||||
toSeriesData, |
||||
DataQueryError, |
||||
} from '@grafana/ui'; |
||||
import { getProcessedSeriesData } from './PanelQueryRunner'; |
||||
import { getBackendSrv } from 'app/core/services/backend_srv'; |
||||
import isEqual from 'lodash/isEqual'; |
||||
|
||||
export class PanelQueryState { |
||||
// The current/last running request
|
||||
request = { |
||||
startTime: 0, |
||||
endTime: 1000, // Somethign not zero
|
||||
} as DataQueryRequest; |
||||
|
||||
// The best known state of data
|
||||
data = { |
||||
state: LoadingState.NotStarted, |
||||
series: [], |
||||
} as PanelData; |
||||
|
||||
sendSeries = false; |
||||
sendLegacy = false; |
||||
|
||||
// A promise for the running query
|
||||
private executor: Promise<PanelData> = {} as any; |
||||
private rejector = (reason?: any) => {}; |
||||
private datasource: DataSourceApi = {} as any; |
||||
|
||||
isRunning() { |
||||
return this.data.state === LoadingState.Loading; //
|
||||
} |
||||
|
||||
isSameQuery(ds: DataSourceApi, req: DataQueryRequest) { |
||||
if (this.datasource !== this.datasource) { |
||||
return false; |
||||
} |
||||
|
||||
// For now just check that the targets look the same
|
||||
return isEqual(this.request.targets, req.targets); |
||||
} |
||||
|
||||
getCurrentExecutor() { |
||||
return this.executor; |
||||
} |
||||
|
||||
cancel(reason: string) { |
||||
const { request } = this; |
||||
try { |
||||
if (!request.endTime) { |
||||
request.endTime = Date.now(); |
||||
|
||||
this.rejector('Canceled:' + reason); |
||||
} |
||||
|
||||
// Cancel any open HTTP request with the same ID
|
||||
if (request.requestId) { |
||||
getBackendSrv().resolveCancelerIfExists(request.requestId); |
||||
} |
||||
} catch (err) { |
||||
console.log('Error canceling request'); |
||||
} |
||||
} |
||||
|
||||
execute(ds: DataSourceApi, req: DataQueryRequest): Promise<PanelData> { |
||||
this.request = req; |
||||
|
||||
console.log('EXXXX', req); |
||||
|
||||
// Return early if there are no queries to run
|
||||
if (!req.targets.length) { |
||||
console.log('No queries, so return early'); |
||||
this.request.endTime = Date.now(); |
||||
return Promise.resolve( |
||||
(this.data = { |
||||
state: LoadingState.Done, |
||||
series: [], // Clear the data
|
||||
legacy: [], |
||||
request: req, |
||||
}) |
||||
); |
||||
} |
||||
|
||||
// Set the loading state immediatly
|
||||
this.data.state = LoadingState.Loading; |
||||
return (this.executor = new Promise<PanelData>((resolve, reject) => { |
||||
this.rejector = reject; |
||||
|
||||
return ds |
||||
.query(this.request) |
||||
.then(resp => { |
||||
this.request.endTime = Date.now(); |
||||
|
||||
// Make sure we send something back -- called run() w/o subscribe!
|
||||
if (!(this.sendSeries || this.sendLegacy)) { |
||||
this.sendSeries = true; |
||||
} |
||||
|
||||
// Make sure the response is in a supported format
|
||||
const series = this.sendSeries ? getProcessedSeriesData(resp.data) : []; |
||||
const legacy = this.sendLegacy |
||||
? resp.data.map(v => { |
||||
if (isSeriesData(v)) { |
||||
return toLegacyResponseData(v); |
||||
} |
||||
return v; |
||||
}) |
||||
: undefined; |
||||
|
||||
resolve( |
||||
(this.data = { |
||||
state: LoadingState.Done, |
||||
request: this.request, |
||||
series, |
||||
legacy, |
||||
}) |
||||
); |
||||
}) |
||||
.catch(err => { |
||||
resolve(this.setError(err)); |
||||
}); |
||||
})); |
||||
} |
||||
|
||||
/** |
||||
* Make sure all requested formats exist on the data |
||||
*/ |
||||
getDataAfterCheckingFormats(): PanelData { |
||||
const { data, sendLegacy, sendSeries } = this; |
||||
if (sendLegacy && (!data.legacy || !data.legacy.length)) { |
||||
data.legacy = data.series.map(v => toLegacyResponseData(v)); |
||||
} |
||||
if (sendSeries && !data.series.length && data.legacy) { |
||||
data.series = data.legacy.map(v => toSeriesData(v)); |
||||
} |
||||
return this.data; |
||||
} |
||||
|
||||
setError(err: any): PanelData { |
||||
if (!this.request.endTime) { |
||||
this.request.endTime = Date.now(); |
||||
} |
||||
|
||||
return (this.data = { |
||||
...this.data, // Keep any existing data
|
||||
state: LoadingState.Error, |
||||
error: toDataQueryError(err), |
||||
request: this.request, |
||||
}); |
||||
} |
||||
} |
||||
|
||||
export function toDataQueryError(err: any): DataQueryError { |
||||
const error = (err || {}) as DataQueryError; |
||||
if (!error.message) { |
||||
if (typeof err === 'string' || err instanceof String) { |
||||
return { message: err } as DataQueryError; |
||||
} |
||||
|
||||
let message = 'Query error'; |
||||
if (error.message) { |
||||
message = error.message; |
||||
} else if (error.data && error.data.message) { |
||||
message = error.data.message; |
||||
} else if (error.data && error.data.error) { |
||||
message = error.data.error; |
||||
} else if (error.status) { |
||||
message = `Query error: ${error.status} ${error.statusText}`; |
||||
} |
||||
error.message = message; |
||||
} |
||||
return error; |
||||
} |
@ -0,0 +1,40 @@ |
||||
import { DataSourceApi, DataQueryRequest, DataQueryResponse } from '@grafana/ui'; |
||||
|
||||
export class DatasourceSrvMock { |
||||
constructor(private defaultDS: DataSourceApi, private datasources: { [name: string]: DataSourceApi }) { |
||||
//
|
||||
} |
||||
|
||||
get(name?: string): Promise<DataSourceApi> { |
||||
if (!name) { |
||||
return Promise.resolve(this.defaultDS); |
||||
} |
||||
const ds = this.datasources[name]; |
||||
if (ds) { |
||||
return Promise.resolve(ds); |
||||
} |
||||
return Promise.reject('Unknown Datasource: ' + name); |
||||
} |
||||
} |
||||
|
||||
export class MockDataSourceApi implements DataSourceApi { |
||||
name: string; |
||||
|
||||
result: DataQueryResponse = { data: [] }; |
||||
queryResolver: Promise<DataQueryResponse>; |
||||
|
||||
constructor(DataQueryResponse, name?: string) { |
||||
this.name = name ? name : 'MockDataSourceApi'; |
||||
} |
||||
|
||||
query(request: DataQueryRequest): Promise<DataQueryResponse> { |
||||
if (this.queryResolver) { |
||||
return this.queryResolver; |
||||
} |
||||
return Promise.resolve(this.result); |
||||
} |
||||
|
||||
testDatasource() { |
||||
return Promise.resolve(); |
||||
} |
||||
} |
Loading…
Reference in new issue