mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
7d78d3f8b2
commit
0a664faeeb
@ -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"],
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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', () => {
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user