Graphite: Introduce new query types in annotation editor (#52341)

* Fix comment lines

* Introduce graphite variable editor

* Render results differently based on queryType

* Add comments and fallback values

* Unit tests for different type of graphite queries

* Use metrics/expand endpoint to retrieve metric names
This commit is contained in:
ismail simsek 2022-07-26 18:30:23 +02:00 committed by GitHub
parent 7d78d3f8b2
commit 0a664faeeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 245 additions and 13 deletions

View File

@ -7083,7 +7083,11 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "27"],
[0, 0, 0, "Unexpected any. Specify a different type.", "28"],
[0, 0, 0, "Unexpected any. Specify a different type.", "29"],
[0, 0, 0, "Unexpected any. Specify a different type.", "30"]
[0, 0, 0, "Unexpected any. Specify a different type.", "30"],
[0, 0, 0, "Unexpected any. Specify a different type.", "31"],
[0, 0, 0, "Unexpected any. Specify a different type.", "32"],
[0, 0, 0, "Unexpected any. Specify a different type.", "33"],
[0, 0, 0, "Unexpected any. Specify a different type.", "34"]
],
"public/app/plugins/datasource/graphite/datasource.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@ -7137,9 +7141,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "48"],
[0, 0, 0, "Unexpected any. Specify a different type.", "49"],
[0, 0, 0, "Unexpected any. Specify a different type.", "50"],
[0, 0, 0, "Do not use any type assertions.", "51"],
[0, 0, 0, "Unexpected any. Specify a different type.", "51"],
[0, 0, 0, "Do not use any type assertions.", "52"],
[0, 0, 0, "Unexpected any. Specify a different type.", "53"],
[0, 0, 0, "Do not use any type assertions.", "53"],
[0, 0, 0, "Unexpected any. Specify a different type.", "54"],
[0, 0, 0, "Unexpected any. Specify a different type.", "55"],
[0, 0, 0, "Unexpected any. Specify a different type.", "56"],
@ -7150,7 +7154,8 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "61"],
[0, 0, 0, "Unexpected any. Specify a different type.", "62"],
[0, 0, 0, "Unexpected any. Specify a different type.", "63"],
[0, 0, 0, "Unexpected any. Specify a different type.", "64"]
[0, 0, 0, "Unexpected any. Specify a different type.", "64"],
[0, 0, 0, "Unexpected any. Specify a different type.", "65"]
],
"public/app/plugins/datasource/graphite/datasource_integration.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],

View File

@ -94,9 +94,9 @@ export class DataSourcePlugin<
return this.setQueryEditorHelp(ExploreStartPage);
}
/*
/**
* @deprecated -- prefer using {@link StandardVariableSupport} or {@link CustomVariableSupport} or {@link DataSourceVariableSupport} in data source instead
* */
*/
setVariableQueryEditor(VariableQueryEditor: any) {
this.components.VariableQueryEditor = VariableQueryEditor;
return this;

View File

@ -0,0 +1,55 @@
import React, { useState } from 'react';
import { InlineField, Input, Select } from '@grafana/ui';
import { GraphiteQuery, GraphiteQueryType } from '../types';
import { convertToGraphiteQueryObject } from './helpers';
interface Props {
query: GraphiteQuery | string;
onChange: (query: GraphiteQuery) => void;
}
const GRAPHITE_QUERY_VARIABLE_TYPE_OPTIONS = [
{ label: 'Default Query', value: GraphiteQueryType.Default },
{ label: 'Value Query', value: GraphiteQueryType.Value },
{ label: 'Metric Name Query', value: GraphiteQueryType.MetricName },
];
export const GraphiteVariableEditor: React.FC<Props> = (props) => {
const { query, onChange } = props;
const [value, setValue] = useState(convertToGraphiteQueryObject(query));
return (
<>
<InlineField label="Select query type" labelWidth={20}>
<Select
aria-label="select query type"
options={GRAPHITE_QUERY_VARIABLE_TYPE_OPTIONS}
width={25}
value={value.queryType ?? GraphiteQueryType.Default}
onChange={(selectableValue) => {
onChange({
...value,
queryType: selectableValue.value,
});
}}
/>
</InlineField>
<InlineField label="Select query type" labelWidth={20} grow>
<Input
aria-label="Variable editor query input"
value={value.target}
onBlur={() => onChange(value)}
onChange={(e) => {
setValue({
...value,
target: e.currentTarget.value,
});
}}
/>
</InlineField>
</>
);
};

View File

@ -3,7 +3,7 @@ import { forEach, sortBy } from 'lodash';
import { SelectableValue } from '@grafana/data';
import { FuncDefs, FuncInstance, ParamDef } from '../gfunc';
import { GraphiteSegment } from '../types';
import { GraphiteQuery, GraphiteQueryType, GraphiteSegment } from '../types';
import { EditableParam } from './FunctionParamEditor';
@ -78,3 +78,14 @@ export function mapFuncInstanceToParams(func: FuncInstance): EditableParam[] {
return params;
}
export function convertToGraphiteQueryObject(query: string | GraphiteQuery): GraphiteQuery {
if (typeof query === 'string') {
return {
refId: 'A',
target: query,
queryType: GraphiteQueryType.Default.toString(),
};
}
return query;
}

View File

@ -8,6 +8,7 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
import { fromString } from './configuration/parseLokiLabelMappings';
import { GraphiteDatasource } from './datasource';
import { GraphiteQuery, GraphiteQueryType } from './types';
import { DEFAULT_GRAPHITE_VERSION } from './versions';
jest.mock('@grafana/runtime', () => ({
@ -534,6 +535,106 @@ describe('graphiteDatasource', () => {
expect(requestOptions.params.query).toBe('*.servers.*');
expect(results).not.toBe(null);
});
it('should fetch from /metrics/find endpoint when queryType is default or query is string', async () => {
const stringQuery = 'query';
ctx.ds.metricFindQuery(stringQuery).then((data: any) => {
results = data;
});
expect(requestOptions.url).toBe('/api/datasources/proxy/1/metrics/find');
expect(results).not.toBe(null);
const objectQuery = {
queryType: GraphiteQueryType.Default,
target: 'query',
refId: 'A',
datasource: ctx.ds,
};
const data = await ctx.ds.metricFindQuery(objectQuery);
expect(requestOptions.url).toBe('/api/datasources/proxy/1/metrics/find');
expect(data).toBeTruthy();
});
it('should fetch from /render endpoint when queryType is value', async () => {
fetchMock.mockImplementation((options: any) => {
requestOptions = options;
return of(
createFetchResponse([
{
target: 'query',
datapoints: [
[10, 1],
[12, 1],
],
},
])
);
});
const fq: GraphiteQuery = {
queryType: GraphiteQueryType.Value,
target: 'query',
refId: 'A',
datasource: ctx.ds,
};
const data = await ctx.ds.metricFindQuery(fq);
expect(requestOptions.url).toBe('/api/datasources/proxy/1/render');
expect(data).toBeTruthy();
});
it('should return values of a query when queryType is GraphiteQueryType.Value', async () => {
fetchMock.mockImplementation((options: any) => {
requestOptions = options;
return of(
createFetchResponse([
{
target: 'query',
datapoints: [
[10, 1],
[12, 1],
],
},
])
);
});
const fq: GraphiteQuery = {
queryType: GraphiteQueryType.Value,
target: 'query',
refId: 'A',
datasource: ctx.ds,
};
const data = await ctx.ds.metricFindQuery(fq);
expect(requestOptions.url).toBe('/api/datasources/proxy/1/render');
expect(data[0].value).toBe(10);
expect(data[0].text).toBe('10');
expect(data[1].value).toBe(12);
expect(data[1].text).toBe('12');
});
it('should return metric names when queryType is GraphiteQueryType.MetricName', async () => {
fetchMock.mockImplementation((options: any) => {
requestOptions = options;
return of(
createFetchResponse({
results: ['apps.backend.backend_01', 'apps.backend.backend_02', 'apps.country.IE', 'apps.country.SE'],
})
);
});
const fq: GraphiteQuery = {
queryType: GraphiteQueryType.MetricName,
target: 'query',
refId: 'A',
datasource: ctx.ds,
};
const data = await ctx.ds.metricFindQuery(fq);
expect(requestOptions.url).toBe('/api/datasources/proxy/1/metrics/expand');
expect(data[0].text).toBe('apps.backend.backend_01');
expect(data[1].text).toBe('apps.backend.backend_02');
expect(data[2].text).toBe('apps.country.IE');
expect(data[3].text).toBe('apps.country.SE');
});
});
describe('exporting to abstract query', () => {

View File

@ -3,15 +3,16 @@ import { lastValueFrom, Observable, of, OperatorFunction, pipe, throwError } fro
import { catchError, map } from 'rxjs/operators';
import {
AbstractLabelMatcher,
AbstractLabelOperator,
AbstractQuery,
DataFrame,
DataQueryRequest,
DataQueryResponse,
DataSourceApi,
DataSourceWithQueryExportSupport,
dateMath,
AbstractQuery,
AbstractLabelOperator,
AbstractLabelMatcher,
dateTime,
MetricFindValue,
QueryResultMetaStat,
ScopedVars,
@ -25,6 +26,7 @@ import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/data
import { getSearchFilterScopedVar } from '../../../features/variables/utils';
import { convertToGraphiteQueryObject } from './components/helpers';
import gfunc, { FuncDefs, FuncInstance } from './gfunc';
import GraphiteQueryModel from './graphite_query';
// Types
@ -34,6 +36,7 @@ import {
GraphiteOptions,
GraphiteQuery,
GraphiteQueryImportConfiguration,
GraphiteQueryType,
GraphiteType,
MetricTankRequestMeta,
} from './types';
@ -74,6 +77,7 @@ export class GraphiteDatasource
funcDefs: FuncDefs | null = null;
funcDefsPromise: Promise<any> | null = null;
_seriesRefLetters: string;
requestCounter = 100;
private readonly metricMappings: GraphiteLokiMapping[];
constructor(instanceSettings: any, private readonly templateSrv: TemplateSrv = getTemplateSrv()) {
@ -182,7 +186,7 @@ export class GraphiteDatasource
from: this.translateTime(options.range.from, false, options.timezone),
until: this.translateTime(options.range.to, true, options.timezone),
targets: options.targets,
format: (options as any).format,
format: (options as any).format ?? 'json',
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
maxDataPoints: options.maxDataPoints,
};
@ -452,9 +456,16 @@ export class GraphiteDatasource
return date.unix();
}
metricFindQuery(query: string, optionalOptions?: any): Promise<MetricFindValue[]> {
metricFindQuery(findQuery: string | GraphiteQuery, optionalOptions?: any): Promise<MetricFindValue[]> {
const options: any = optionalOptions || {};
const queryObject = convertToGraphiteQueryObject(findQuery);
if (queryObject.queryType === GraphiteQueryType.Value) {
return this.requestMetricRender(queryObject, options);
}
let query = queryObject.target ?? '';
// First attempt to check for tag-related functions (using empty wildcard for interpolation)
let interpolatedQuery = this.templateSrv.replace(
query,
@ -494,13 +505,54 @@ export class GraphiteDatasource
};
}
if (useExpand) {
if (useExpand || queryObject.queryType === GraphiteQueryType.MetricName) {
return this.requestMetricExpand(interpolatedQuery, options.requestId, range);
} else {
return this.requestMetricFind(interpolatedQuery, options.requestId, range);
}
}
/**
* Search for metrics matching giving pattern using /metrics/render endpoint.
* It will return all possible values and parse them based on queryType.
* For example:
*
* queryType: GraphiteQueryType.Value
* query: groupByNode(movingAverage(apps.country.IE.counters.requests.count, 10), 2, 'sum')
* result: 239.4, 233.4, 230.8, 230.4, 233.9, 238, 239.8, 236.8, 235.8
*/
private async requestMetricRender(queryObject: GraphiteQuery, options: any): Promise<MetricFindValue[]> {
const requestId: string = options.requestId ?? `Q${this.requestCounter++}`;
const range: TimeRange = options.range ?? {
from: dateTime().subtract(6, 'hour'),
to: dateTime(),
raw: {
from: 'now - 6h',
to: 'now',
},
};
const queryReq: DataQueryRequest<GraphiteQuery> = {
app: 'graphite-variable-editor',
interval: '1s',
intervalMs: 10000,
startTime: Date.now(),
targets: [{ ...queryObject }],
timezone: 'browser',
scopedVars: {},
requestId,
range,
};
const data = await lastValueFrom(this.query(queryReq));
const result: MetricFindValue[] = data.data[0].fields[1].values
.filter((f?: number) => !!f)
.map((v: number) => ({
text: v.toString(),
value: v,
expandable: false,
}));
return Promise.resolve(result);
}
/**
* Search for metrics matching giving pattern using /metrics/find endpoint. It will
* return all possible values at the last level of the query, for example:

View File

@ -1,6 +1,7 @@
import { DataSourcePlugin } from '@grafana/data';
import { GraphiteQueryEditor } from './components/GraphiteQueryEditor';
import { GraphiteVariableEditor } from './components/GraphiteVariableEditor';
import { MetricTankMetaInspector } from './components/MetricTankMetaInspector';
import { ConfigEditor } from './configuration/ConfigEditor';
import { GraphiteDatasource } from './datasource';
@ -12,5 +13,6 @@ class AnnotationsQueryCtrl {
export const plugin = new DataSourcePlugin(GraphiteDatasource)
.setQueryEditor(GraphiteQueryEditor)
.setConfigEditor(ConfigEditor)
.setVariableQueryEditor(GraphiteVariableEditor)
.setMetadataInspector(MetricTankMetaInspector)
.setAnnotationQueryCtrl(AnnotationsQueryCtrl);

View File

@ -4,6 +4,12 @@ import { TemplateSrv } from '../../../features/templating/template_srv';
import { GraphiteDatasource } from './datasource';
export enum GraphiteQueryType {
Default = 'Default',
Value = 'Value',
MetricName = 'Metric Name',
}
export interface GraphiteQuery extends DataQuery {
target?: string;
}