mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
257 lines
7.0 KiB
TypeScript
257 lines
7.0 KiB
TypeScript
import _ from 'lodash';
|
|
|
|
import * as dateMath from 'app/core/utils/datemath';
|
|
|
|
import {
|
|
getAnnotationsFromResult,
|
|
getTableModelFromResult,
|
|
getTimeSeriesFromResult,
|
|
getValuesFromResult,
|
|
parseResults,
|
|
} from './response_parser';
|
|
import expandMacros from './metric_find_query';
|
|
|
|
function serializeParams(params) {
|
|
if (!params) {
|
|
return '';
|
|
}
|
|
|
|
return _.reduce(
|
|
params,
|
|
(memo, value, key) => {
|
|
if (value === null || value === undefined) {
|
|
return memo;
|
|
}
|
|
memo.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
|
|
return memo;
|
|
},
|
|
[]
|
|
).join('&');
|
|
}
|
|
|
|
const MAX_SERIES = 20;
|
|
export default class InfluxDatasource {
|
|
type: string;
|
|
url: string;
|
|
username: string;
|
|
password: string;
|
|
name: string;
|
|
orgName: string;
|
|
database: any;
|
|
basicAuth: any;
|
|
withCredentials: any;
|
|
interval: any;
|
|
supportAnnotations: boolean;
|
|
supportMetrics: boolean;
|
|
|
|
/** @ngInject */
|
|
constructor(instanceSettings, private backendSrv, private templateSrv) {
|
|
this.type = 'influxdb-ifql';
|
|
this.url = instanceSettings.url.trim();
|
|
|
|
this.username = instanceSettings.username;
|
|
this.password = instanceSettings.password;
|
|
this.name = instanceSettings.name;
|
|
this.orgName = instanceSettings.orgName || 'defaultorgname';
|
|
this.database = instanceSettings.database;
|
|
this.basicAuth = instanceSettings.basicAuth;
|
|
this.withCredentials = instanceSettings.withCredentials;
|
|
this.interval = (instanceSettings.jsonData || {}).timeInterval;
|
|
this.supportAnnotations = true;
|
|
this.supportMetrics = true;
|
|
}
|
|
|
|
prepareQueryTarget(target, options) {
|
|
// Replace grafana variables
|
|
const timeFilter = this.getTimeFilter(options);
|
|
options.scopedVars.range = { value: timeFilter };
|
|
const interpolated = this.templateSrv.replace(target.query, options.scopedVars);
|
|
return {
|
|
...target,
|
|
query: interpolated,
|
|
};
|
|
}
|
|
|
|
query(options) {
|
|
const queryTargets = options.targets
|
|
.filter(target => target.query)
|
|
.map(target => this.prepareQueryTarget(target, options));
|
|
if (queryTargets.length === 0) {
|
|
return Promise.resolve({ data: [] });
|
|
}
|
|
|
|
const queries = queryTargets.map(target => {
|
|
const { query, resultFormat } = target;
|
|
|
|
if (resultFormat === 'table') {
|
|
return this._seriesQuery(query, options)
|
|
.then(response => parseResults(response.data))
|
|
.then(results => results.map(getTableModelFromResult));
|
|
} else {
|
|
return this._seriesQuery(query, options)
|
|
.then(response => parseResults(response.data))
|
|
.then(results => results.map(getTimeSeriesFromResult));
|
|
}
|
|
});
|
|
|
|
return Promise.all(queries).then((series: any) => {
|
|
let seriesList = _.flattenDeep(series).slice(0, MAX_SERIES);
|
|
return { data: seriesList };
|
|
});
|
|
}
|
|
|
|
annotationQuery(options) {
|
|
if (!options.annotation.query) {
|
|
return Promise.reject({
|
|
message: 'Query missing in annotation definition',
|
|
});
|
|
}
|
|
|
|
const { query } = options.annotation;
|
|
const queryOptions = {
|
|
scopedVars: {},
|
|
...options,
|
|
silent: true,
|
|
};
|
|
const target = this.prepareQueryTarget({ query }, queryOptions);
|
|
|
|
return this._seriesQuery(target.query, queryOptions).then(response => {
|
|
const results = parseResults(response.data);
|
|
if (results.length === 0) {
|
|
throw { message: 'No results in response from InfluxDB' };
|
|
}
|
|
const annotations = _.flatten(results.map(result => getAnnotationsFromResult(result, options.annotation)));
|
|
return annotations;
|
|
});
|
|
}
|
|
|
|
metricFindQuery(query: string, options?: any) {
|
|
const interpreted = expandMacros(query);
|
|
|
|
// Use normal querier in silent mode
|
|
const queryOptions = {
|
|
rangeRaw: { to: 'now', from: 'now - 1h' },
|
|
scopedVars: {},
|
|
...options,
|
|
silent: true,
|
|
};
|
|
const target = this.prepareQueryTarget({ query: interpreted }, queryOptions);
|
|
return this._seriesQuery(target.query, queryOptions).then(response => {
|
|
const results = parseResults(response.data);
|
|
const values = _.uniq(_.flatten(results.map(getValuesFromResult)));
|
|
return values
|
|
.filter(value => value && value[0] !== '_') // Ignore internal fields
|
|
.map(value => ({ text: value }));
|
|
});
|
|
}
|
|
|
|
_seriesQuery(query: string, options?: any) {
|
|
if (!query) {
|
|
return Promise.resolve({ data: '' });
|
|
}
|
|
return this._influxRequest('POST', '/v1/query', { q: query }, options);
|
|
}
|
|
|
|
testDatasource() {
|
|
const query = `from(db:"${this.database}") |> last()`;
|
|
|
|
return this._influxRequest('POST', '/v1/query', { q: query })
|
|
.then(res => {
|
|
if (res && res.data && res.data.trim()) {
|
|
return { status: 'success', message: 'Data source connected and database found.' };
|
|
}
|
|
return {
|
|
status: 'error',
|
|
message:
|
|
'Data source connected, but has no data. Verify the "Database" field and make sure the database has data.',
|
|
};
|
|
})
|
|
.catch(err => {
|
|
return { status: 'error', message: err.message };
|
|
});
|
|
}
|
|
|
|
_influxRequest(method: string, url: string, data: any, options?: any) {
|
|
let params: any = {
|
|
orgName: this.orgName,
|
|
};
|
|
|
|
if (this.username) {
|
|
params.u = this.username;
|
|
params.p = this.password;
|
|
}
|
|
|
|
// data sent as GET param
|
|
_.extend(params, data);
|
|
data = null;
|
|
|
|
let req: any = {
|
|
method: method,
|
|
url: this.url + url,
|
|
params: params,
|
|
data: data,
|
|
precision: 'ms',
|
|
inspect: { type: this.type },
|
|
paramSerializer: serializeParams,
|
|
};
|
|
|
|
req.headers = req.headers || {};
|
|
if (this.basicAuth || this.withCredentials) {
|
|
req.withCredentials = true;
|
|
}
|
|
if (this.basicAuth) {
|
|
req.headers.Authorization = this.basicAuth;
|
|
}
|
|
|
|
return this.backendSrv.datasourceRequest(req).then(
|
|
result => {
|
|
return result;
|
|
},
|
|
function(err) {
|
|
if (err.status !== 0 || err.status >= 300) {
|
|
if (err.data && err.data.error) {
|
|
throw {
|
|
message: 'InfluxDB Error: ' + err.data.error,
|
|
data: err.data,
|
|
config: err.config,
|
|
};
|
|
} else {
|
|
throw {
|
|
message: 'Network Error: ' + err.statusText + '(' + err.status + ')',
|
|
data: err.data,
|
|
config: err.config,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
getTimeFilter(options) {
|
|
const from = this.getInfluxTime(options.rangeRaw.from, false);
|
|
const to = this.getInfluxTime(options.rangeRaw.to, true);
|
|
if (to === 'now') {
|
|
return `start: ${from}`;
|
|
}
|
|
return `start: ${from}, stop: ${to}`;
|
|
}
|
|
|
|
getInfluxTime(date, roundUp) {
|
|
if (_.isString(date)) {
|
|
if (date === 'now') {
|
|
return date;
|
|
}
|
|
|
|
const parts = /^now\s*-\s*(\d+)([d|h|m|s])$/.exec(date);
|
|
if (parts) {
|
|
const amount = parseInt(parts[1]);
|
|
const unit = parts[2];
|
|
return '-' + amount + unit;
|
|
}
|
|
date = dateMath.parse(date, roundUp);
|
|
}
|
|
|
|
return date.toISOString();
|
|
}
|
|
}
|