QueryService refactoring #6
Merged
rozetko
merged 1 commits from query-service-refactoring
into master
2 years ago
23 changed files with 250 additions and 197 deletions
@ -1,5 +1,5 @@ |
|||||||
import { SqlMetric } from './sql'; |
import { SqlConnector } from './sql'; |
||||||
|
|
||||||
export class MysqlMetric extends SqlMetric { |
export class MysqlConnector extends SqlConnector { |
||||||
|
|
||||||
} |
} |
||||||
|
@ -1,5 +1,5 @@ |
|||||||
import { SqlMetric } from './sql'; |
import { SqlConnector } from './sql'; |
||||||
|
|
||||||
export class PostgresMetric extends SqlMetric { |
export class PostgresConnector extends SqlConnector { |
||||||
|
|
||||||
} |
} |
||||||
|
@ -1,48 +0,0 @@ |
|||||||
import { DatasourceUnavailable } from '../types'; |
|
||||||
import { Datasource, DatasourceQuery } from '../connectors'; |
|
||||||
|
|
||||||
import axios from 'axios'; |
|
||||||
import * as _ from 'lodash'; |
|
||||||
|
|
||||||
|
|
||||||
// TODO: support direct queries auth
|
|
||||||
// TODO: move to class and inherit from QueryService abstract class
|
|
||||||
export async function queryDirect(query: DatasourceQuery, datasource: Datasource) { |
|
||||||
let axiosQuery = { |
|
||||||
url: query.url, |
|
||||||
method: query.method, |
|
||||||
}; |
|
||||||
console.log(axiosQuery) |
|
||||||
|
|
||||||
_.defaults(axiosQuery, query.schema); |
|
||||||
|
|
||||||
try { |
|
||||||
return axios(axiosQuery); |
|
||||||
} catch (e) { |
|
||||||
// TODO: seems like this error handler can be used for both Grafana and Direct queries
|
|
||||||
const msg = `TSDB-kit: fail while request data: ${e.message}`; |
|
||||||
const parsedUrl = new URL(query.url); |
|
||||||
const queryUrl = `query url: ${JSON.stringify(parsedUrl.pathname)}`; |
|
||||||
console.error(`${msg} ${queryUrl}`); |
|
||||||
|
|
||||||
if(e.response !== undefined) { |
|
||||||
console.error(`Response: \ |
|
||||||
status: ${e.response.status}, \ |
|
||||||
response data: ${JSON.stringify(e.response.data)}, \ |
|
||||||
headers: ${JSON.stringify(e.response.headers)} |
|
||||||
`);
|
|
||||||
if(e.response.status === 401) { |
|
||||||
throw new Error(`Unauthorized. Check credentials. ${e.message}`); |
|
||||||
} |
|
||||||
if(e.response.status === 502) { |
|
||||||
let datasourceError = new DatasourceUnavailable( |
|
||||||
`datasource ${parsedUrl.pathname} unavailable, message: ${e.message}`, |
|
||||||
datasource.type, |
|
||||||
query.url |
|
||||||
); |
|
||||||
throw datasourceError; |
|
||||||
} |
|
||||||
} |
|
||||||
throw new Error(msg); |
|
||||||
} |
|
||||||
} |
|
@ -1,77 +0,0 @@ |
|||||||
import { Datasource, DatasourceQuery } from '../connectors'; |
|
||||||
import { TsdbKitError, DatasourceUnavailable } from '../types'; |
|
||||||
|
|
||||||
import axios from 'axios'; |
|
||||||
import * as _ from 'lodash'; |
|
||||||
|
|
||||||
|
|
||||||
export class GrafanaUnavailable extends TsdbKitError { }; |
|
||||||
|
|
||||||
// TODO: move to class and inherit from QueryService abstract class
|
|
||||||
export async function queryGrafana(query: DatasourceQuery, apiKey: string, datasource: Datasource) { |
|
||||||
let headers = { Authorization: `Bearer ${apiKey}` }; |
|
||||||
|
|
||||||
const grafanaUrl = getGrafanaUrl(query.url); |
|
||||||
query.url = `${grafanaUrl}/${query.url}`; |
|
||||||
|
|
||||||
if(query.headers !== undefined) { |
|
||||||
_.merge(headers, query.headers); |
|
||||||
} |
|
||||||
|
|
||||||
let axiosQuery = { |
|
||||||
headers, |
|
||||||
url: query.url, |
|
||||||
method: query.method, |
|
||||||
}; |
|
||||||
|
|
||||||
_.defaults(axiosQuery, query.schema); |
|
||||||
|
|
||||||
try { |
|
||||||
return axios(axiosQuery); |
|
||||||
} catch (e) { |
|
||||||
// TODO: seems like this error handler can be used for both Grafana and Direct queries
|
|
||||||
const msg = `TSDB-kit: fail while request data: ${e.message}`; |
|
||||||
const parsedUrl = new URL(query.url); |
|
||||||
const queryUrl = `query url: ${JSON.stringify(parsedUrl.pathname)}`; |
|
||||||
console.error(`${msg} ${queryUrl}`); |
|
||||||
if(e.errno === 'ECONNREFUSED') { |
|
||||||
throw new GrafanaUnavailable(e.message); |
|
||||||
} |
|
||||||
if(e.response !== undefined) { |
|
||||||
console.error(`Response: \ |
|
||||||
status: ${e.response.status}, \ |
|
||||||
response data: ${JSON.stringify(e.response.data)}, \ |
|
||||||
headers: ${JSON.stringify(e.response.headers)} |
|
||||||
`);
|
|
||||||
if(e.response.status === 401) { |
|
||||||
throw new Error(`Unauthorized. Check the API_KEY. ${e.message}`); |
|
||||||
} |
|
||||||
if(e.response.status === 502) { |
|
||||||
let datasourceError = new DatasourceUnavailable( |
|
||||||
`datasource ${parsedUrl.pathname} unavailable, message: ${e.message}`, |
|
||||||
datasource.type, |
|
||||||
query.url |
|
||||||
); |
|
||||||
throw datasourceError; |
|
||||||
} |
|
||||||
} |
|
||||||
throw new Error(msg); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function getGrafanaUrl(url: string): string { |
|
||||||
const parsedUrl = new URL(url); |
|
||||||
const path = parsedUrl.pathname; |
|
||||||
const panelUrl = path.match(/^\/*([^\/]*)\/d\//); |
|
||||||
if(panelUrl === null) { |
|
||||||
return url; |
|
||||||
} |
|
||||||
|
|
||||||
const origin = parsedUrl.origin; |
|
||||||
const grafanaSubPath = panelUrl[1]; |
|
||||||
if(grafanaSubPath.length > 0) { |
|
||||||
return `${origin}/${grafanaSubPath}`; |
|
||||||
} |
|
||||||
|
|
||||||
return origin; |
|
||||||
} |
|
@ -0,0 +1,11 @@ |
|||||||
|
import { Datasource, DatasourceQuery } from '../../connectors'; |
||||||
|
|
||||||
|
import { AxiosResponse } from 'axios'; |
||||||
|
|
||||||
|
|
||||||
|
export abstract class QueryService { |
||||||
|
constructor(protected _datasource: Datasource) { } |
||||||
|
|
||||||
|
// TODO: we don't need `apiKey` here, we need some abstract auth config for both Direct and Grafana queries
|
||||||
|
abstract query(query: DatasourceQuery, apiKey?: string): Promise<AxiosResponse<any>>; |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
import { QueryService } from './base'; |
||||||
|
import { DatasourceUnavailable } from '../../types'; |
||||||
|
import { Datasource, DatasourceQuery } from '../../connectors'; |
||||||
|
|
||||||
|
import axios, { AxiosResponse } from 'axios'; |
||||||
|
import * as _ from 'lodash'; |
||||||
|
|
||||||
|
|
||||||
|
export class DirectQueryService extends QueryService { |
||||||
|
constructor(datasource: Datasource) { |
||||||
|
super(datasource); |
||||||
|
} |
||||||
|
|
||||||
|
async query(query: DatasourceQuery): Promise<AxiosResponse<any>> { |
||||||
|
// TODO: support auth
|
||||||
|
let axiosQuery = { |
||||||
|
url: query.url, |
||||||
|
method: query.method, |
||||||
|
}; |
||||||
|
|
||||||
|
_.defaults(axiosQuery, query.schema); |
||||||
|
|
||||||
|
try { |
||||||
|
return axios(axiosQuery); |
||||||
|
} catch(e) { |
||||||
|
// TODO: seems like this error handler can be used for both Grafana and Direct queries
|
||||||
|
const msg = `TSDB-kit: fail while request data: ${e.message}`; |
||||||
|
const parsedUrl = new URL(query.url); |
||||||
|
const queryUrl = `query url: ${JSON.stringify(parsedUrl.pathname)}`; |
||||||
|
console.error(`${msg} ${queryUrl}`); |
||||||
|
|
||||||
|
if(e.response !== undefined) { |
||||||
|
console.error(`Response: \ |
||||||
|
status: ${e.response.status}, \ |
||||||
|
response data: ${JSON.stringify(e.response.data)}, \ |
||||||
|
headers: ${JSON.stringify(e.response.headers)} |
||||||
|
`);
|
||||||
|
if(e.response.status === 401) { |
||||||
|
throw new Error(`Unauthorized. Check credentials. ${e.message}`); |
||||||
|
} |
||||||
|
if(e.response.status === 502) { |
||||||
|
let datasourceError = new DatasourceUnavailable( |
||||||
|
`datasource ${parsedUrl.pathname} unavailable, message: ${e.message}`, |
||||||
|
this._datasource.type, |
||||||
|
query.url |
||||||
|
); |
||||||
|
throw datasourceError; |
||||||
|
} |
||||||
|
} |
||||||
|
throw new Error(msg); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
import { QueryService } from './base'; |
||||||
|
import { Datasource, DatasourceQuery } from '../../connectors'; |
||||||
|
import { TsdbKitError, DatasourceUnavailable } from '../../types'; |
||||||
|
|
||||||
|
import axios, { AxiosResponse } from 'axios'; |
||||||
|
import * as _ from 'lodash'; |
||||||
|
|
||||||
|
|
||||||
|
export class GrafanaUnavailable extends TsdbKitError { }; |
||||||
|
|
||||||
|
export class GrafanaQueryService extends QueryService { |
||||||
|
constructor(datasource: Datasource) { |
||||||
|
super(datasource); |
||||||
|
} |
||||||
|
|
||||||
|
async query(query: DatasourceQuery, apiKey: string): Promise<AxiosResponse<any>> { |
||||||
|
let headers = { Authorization: `Bearer ${apiKey}` }; |
||||||
|
|
||||||
|
const grafanaUrl = getGrafanaUrl(query.url); |
||||||
|
query.url = `${grafanaUrl}/${query.url}`; |
||||||
|
|
||||||
|
if(query.headers !== undefined) { |
||||||
|
_.merge(headers, query.headers); |
||||||
|
} |
||||||
|
|
||||||
|
let axiosQuery = { |
||||||
|
headers, |
||||||
|
url: query.url, |
||||||
|
method: query.method, |
||||||
|
}; |
||||||
|
|
||||||
|
_.defaults(axiosQuery, query.schema); |
||||||
|
|
||||||
|
try { |
||||||
|
return axios(axiosQuery); |
||||||
|
} catch (e) { |
||||||
|
// TODO: seems like this error handler can be used for both Grafana and Direct queries
|
||||||
|
const msg = `TSDB-kit: fail while request data: ${e.message}`; |
||||||
|
const parsedUrl = new URL(query.url); |
||||||
|
const queryUrl = `query url: ${JSON.stringify(parsedUrl.pathname)}`; |
||||||
|
console.error(`${msg} ${queryUrl}`); |
||||||
|
if(e.errno === 'ECONNREFUSED') { |
||||||
|
throw new GrafanaUnavailable(e.message); |
||||||
|
} |
||||||
|
if(e.response !== undefined) { |
||||||
|
console.error(`Response: \ |
||||||
|
status: ${e.response.status}, \ |
||||||
|
response data: ${JSON.stringify(e.response.data)}, \ |
||||||
|
headers: ${JSON.stringify(e.response.headers)} |
||||||
|
`);
|
||||||
|
if(e.response.status === 401) { |
||||||
|
throw new Error(`Unauthorized. Check the API_KEY. ${e.message}`); |
||||||
|
} |
||||||
|
if(e.response.status === 502) { |
||||||
|
let datasourceError = new DatasourceUnavailable( |
||||||
|
`datasource ${parsedUrl.pathname} unavailable, message: ${e.message}`, |
||||||
|
this._datasource.type, |
||||||
|
query.url |
||||||
|
); |
||||||
|
throw datasourceError; |
||||||
|
} |
||||||
|
} |
||||||
|
throw new Error(msg); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function getGrafanaUrl(url: string): string { |
||||||
|
const parsedUrl = new URL(url); |
||||||
|
const path = parsedUrl.pathname; |
||||||
|
const panelUrl = path.match(/^\/*([^\/]*)\/d\//); |
||||||
|
if(panelUrl === null) { |
||||||
|
return url; |
||||||
|
} |
||||||
|
|
||||||
|
const origin = parsedUrl.origin; |
||||||
|
const grafanaSubPath = panelUrl[1]; |
||||||
|
if(grafanaSubPath.length > 0) { |
||||||
|
return `${origin}/${grafanaSubPath}`; |
||||||
|
} |
||||||
|
|
||||||
|
return origin; |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
import { QueryService } from './base'; |
||||||
|
import { DirectQueryService } from './direct'; |
||||||
|
import { GrafanaQueryService } from './grafana'; |
||||||
|
import { QueryType } from '../../connectors'; |
||||||
|
import { QueryConfig } from '../../models/query_config'; |
||||||
|
|
||||||
|
|
||||||
|
export function queryServiceFactory( |
||||||
|
queryConfig: QueryConfig, |
||||||
|
): QueryService { |
||||||
|
const classMap = { |
||||||
|
[QueryType.DIRECT]: DirectQueryService, |
||||||
|
[QueryType.GRAFANA]: GrafanaQueryService, |
||||||
|
}; |
||||||
|
const queryType = queryConfig.queryType; |
||||||
|
const datasource = queryConfig.datasource; |
||||||
|
if(classMap[queryType] === undefined) { |
||||||
|
console.error(`Queries of type ${queryType} are not supported currently`); |
||||||
|
throw new Error(`Queries of type ${queryType} are not supported currently`); |
||||||
|
} else { |
||||||
|
return new classMap[queryType](datasource); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue