diff --git a/README.md b/README.md index 95c4b64..4be3dfa 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ To learn more about building dashboards, see the [Grafana docs][9]. The Sensu Go Data Source supports query strings with the structure: - QUERY API (entity|events|namespaces) [IN NAMESPACE (namespace)] SELECT (field-key) [WHERE (field-key)(=|!=|=~|!~|<|>)(field-value) [AND (field-key)(=|!=|=~|!~|<|>)(field-value)]] + QUERY API (entity|events|namespaces) [IN NAMESPACE (namespace)] SELECT (field-key) [WHERE (field-key)(=|!=|=~|!~|<|>)(field-value) [AND (field-key)(=|!=|=~|!~|<|>)(field-value)]] [LIMIT (limit)] > Note: Query keywords are case sensitive. @@ -91,10 +91,10 @@ For example, the following query returns hostnames containing the string `webser QUERY API entity SELECT system.hostname WHERE system.hostname=~/webserver/ ``` -The following query returns entity hostnames with active, non-OK events within the `ops` namespace: +The following query returns 100 entity hostnames with active, non-OK events within the `ops` namespace: ``` -QUERY API events IN NAMESPACE ops SELECT entity.system.hostname WHERE check.status>0 +QUERY API events IN NAMESPACE ops SELECT entity.system.hostname WHERE check.status>0 LIMIT 100 ``` The following query returns all namespaces with names starting with `x`: diff --git a/src/constants.ts b/src/constants.ts index 4033de0..8321df2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,6 +2,16 @@ import AggregationType from './model/AggregationType'; import ApiEndpoint from './model/ApiEndpoint'; import TextValue from './model/TextValue'; +/** + * The default limit. + */ +export const DEFAULT_LIMIT: number = 100; + +/** + * The default limit for aggregation queries. + */ +export const DEFAULT_AGGREGATION_LIMIT: number = 0; + /** * Supported aggregation functions. */ diff --git a/src/datasource.ts b/src/datasource.ts index fe13c8e..9171054 100644 --- a/src/datasource.ts +++ b/src/datasource.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import sensu from './sensu/sensu'; import SensuQueryOptions from './sensu/query_options'; -import {API_ENDPOINTS} from './constants'; +import { API_ENDPOINTS, DEFAULT_LIMIT, DEFAULT_AGGREGATION_LIMIT } from './constants'; import FieldSelector from './FieldSelector'; import FilterUtils from './utils/datasource_filter_util'; import QueryUtils from './utils/query_util'; @@ -52,7 +52,7 @@ export default class SensuDatasource { * Returns the url of the API used by the given target. */ _getApiUrl = target => { - const apiEndpoint: any = _.find(API_ENDPOINTS, {value: target.apiEndpoints}); + const apiEndpoint: any = _.find(API_ENDPOINTS, { value: target.apiEndpoints }); if (apiEndpoint) { return apiEndpoint.url; } else { @@ -97,21 +97,31 @@ export default class SensuDatasource { ); if (queryTargets.length === 0) { - return Promise.resolve({data: []}); + return Promise.resolve({ data: [] }); } const queries = queryTargets.map(prepTarget => { const { apiUrl, filters, - target: {queryType, fieldSelectors, namespace, limit}, + target: { queryType, fieldSelectors, namespace, limit }, } = prepTarget; + // verify and set correct limit + let parsedLimit: number = _.defaultTo(parseInt(limit), -1); + if (parsedLimit < 0) { + if (queryType === 'aggregation') { + parsedLimit = DEFAULT_AGGREGATION_LIMIT; + } else { + parsedLimit = DEFAULT_LIMIT; + } + } + const queryOptions: SensuQueryOptions = { method: 'GET', url: apiUrl, namespace: namespace, - limit: limit, + limit: parsedLimit, }; return sensu @@ -131,19 +141,19 @@ export default class SensuDatasource { return Promise.all(queries).then((queryResults: any) => { if (options.resultAsPlainArray) { - // return only values - e.g. for template variables + // return only values - e.g. for template variables return _(queryResults) .map(result => this._transformToTable(result)) .map(result => result.rows) .flatten() .flatten() .map(value => { - return {text: value}; + return { text: value }; }) .value(); } else { const resultDataList: any[] = _.flatMap(queryResults, (queryResult, index) => { - const {target: {format}} = queryTargets[index]; + const { target: { format } } = queryTargets[index]; if (format === 'series') { // return time series format @@ -166,7 +176,7 @@ export default class SensuDatasource { */ _queryAggregation = (data: any[], prepTarget: PreparedTarget) => { const { - target: {aggregationAlias: alias, aggregationType: type, aggregationField: field}, + target: { aggregationAlias: alias, aggregationType: type, aggregationField: field }, } = prepTarget; const name = alias ? alias : type; @@ -433,7 +443,7 @@ export default class SensuDatasource { * Transforms the given query components into an options object which can be used by the `query(..)` function. */ _transformQueryComponentsToQueryOptions = (queryComponents: QueryComponents) => { - const {apiKey, selectedField, filters, namespace} = queryComponents; + const { apiKey, selectedField, filters, namespace, limit } = queryComponents; const filterObjects = _.map(filters, filter => { return [ @@ -455,6 +465,7 @@ export default class SensuDatasource { apiEndpoints: apiKey, queryType: 'field', namespace: namespace, + limit: limit, fieldSelectors: [ { fieldSegments: [ @@ -485,7 +496,7 @@ export default class SensuDatasource { }; }) .catch(err => { - return {status: 'error', message: err.message}; + return { status: 'error', message: err.message }; }); } } diff --git a/src/model/QueryComponents.ts b/src/model/QueryComponents.ts index d4ab6f5..3f42343 100644 --- a/src/model/QueryComponents.ts +++ b/src/model/QueryComponents.ts @@ -5,4 +5,5 @@ export default interface QueryComponents { readonly namespace: string; readonly selectedField: string; readonly filters: Filter[]; + readonly limit: number; }; diff --git a/src/partials/query.editor.html b/src/partials/query.editor.html index f41adea..1e529c2 100644 --- a/src/partials/query.editor.html +++ b/src/partials/query.editor.html @@ -28,7 +28,7 @@
-
+
- -
- - - - -
-
-
-
@@ -120,6 +109,24 @@ + +
+ + + + +
+
+
+
+
diff --git a/src/sensu/query_options.ts b/src/sensu/query_options.ts index 7e31064..5a8c207 100644 --- a/src/sensu/query_options.ts +++ b/src/sensu/query_options.ts @@ -2,6 +2,6 @@ export default interface QueryOptions { method: string; url: string; namespace: string; - limit?: string; + limit: number; forceAccessTokenRefresh?: boolean; }; diff --git a/src/sensu/sensu.ts b/src/sensu/sensu.ts index ac5fe3a..b5d1a22 100644 --- a/src/sensu/sensu.ts +++ b/src/sensu/sensu.ts @@ -26,7 +26,7 @@ export default class Sensu { * @param options the options specifying the query's request */ static query(datasource: any, options: QueryOptions) { - const {method, url, namespace, limit, forceAccessTokenRefresh} = options; + const { method, url, namespace, limit, forceAccessTokenRefresh } = options; if (forceAccessTokenRefresh) { delete datasource.instanceSettings.tokens; } @@ -38,21 +38,13 @@ export default class Sensu { fullUrl = Sensu.apiBaseUrl + '/namespaces/' + namespace + url; } - let queryLimitString: string; - if (limit) { - queryLimitString = limit; - } else { - queryLimitString = '100'; - } - - const queryLimit = _.defaultTo(parseInt(queryLimitString), 100); - if (queryLimit > 0) { - fullUrl += '?limit=' + queryLimit; + if (limit > 0) { + fullUrl += '?limit=' + limit; } return this._authenticate(datasource) .then(() => this._request(datasource, method, fullUrl)) - .catch(() => this.query(datasource, {...options, forceAccessTokenRefresh: true})); + .catch(() => this.query(datasource, { ...options, forceAccessTokenRefresh: true })); } /** diff --git a/src/utils/query_util.ts b/src/utils/query_util.ts index c009650..0e15a43 100644 --- a/src/utils/query_util.ts +++ b/src/utils/query_util.ts @@ -1,13 +1,9 @@ import _ from 'lodash'; +import { DEFAULT_LIMIT, DEFAULT_AGGREGATION_LIMIT } from '../constants'; import QueryComponents from '../model/QueryComponents'; -/** - * The default limit. - */ -const DEFAULT_LIMIT: number = 100; - -const QUERY_FULL_REG_EXP = /QUERY\s+API\s+(entity|events|namespaces)\s+(IN\s+NAMESPACE\s+(\S+)\s+)?SELECT\s+(\S+)(\s+WHERE\s+(\S+\s*(=~?|!=|>|<|!=)\s*\S+(\s+AND\s+\S+\s*(=~?|!=|>|<|!=)\s*\S+)*))?/; +const QUERY_FULL_REG_EXP = /QUERY\s+API\s+(entity|events|namespaces)\s+(IN\s+NAMESPACE\s+(\S+)\s+)?SELECT\s+(\S+)(\s+WHERE\s+(\S+\s*(=~?|!=|>|<|!=)\s*\S+(\s+AND\s+\S+\s*(=~?|!=|>|<|!=)\s*\S+)*))?(\s+LIMIT\s+(\d+))?/; const QUERY_SINGLE_FILTER_REG_EXP = /(\S+)\s*(=~?|!=|>|<|!~)\s*(\S+)/g; /** @@ -100,7 +96,12 @@ const _whereClause = (target: any) => { const _limit = (target: any) => { let queryLimit = target.limit; if (!target.limit) { - queryLimit = DEFAULT_LIMIT; + // Use a special default limit in aggregation queries + if (target.queryType === 'aggregation') { + queryLimit = DEFAULT_AGGREGATION_LIMIT; + } else { + queryLimit = DEFAULT_LIMIT; + } } queryLimit = _.defaultTo(parseInt(queryLimit), DEFAULT_LIMIT); @@ -130,6 +131,7 @@ export const extractQueryComponents = (query: string) => { namespace: namespace, selectedField: matchResult[4], filters: [], + limit: parseInt(matchResult[11]) }; if (matchResult[6] !== undefined) {