mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Graphite Plugin: Remove angular dependencies for graphite annotations (#52261)
* fix merge conflict * fix betterer * handle new creating annotations * add h5 'or' tag to annotation editor * fix annotation regression looking for tags before target * remove angular annotation partial * change ann tags type to string[] and use TagsInput to create ann * remove GraphiteEventsType, return annotations targets setting 'textEditor': true * fix yarn typecheck errors * add dateTime for yarn fix to tests * fix incorrect merge conflict resolution * fix betterer * making changes for PR approval resolutions * fix prettier issue * fix prettier
This commit is contained in:
parent
6ec9a7682d
commit
6c3efb0c88
@ -7215,6 +7215,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
|
||||
],
|
||||
"public/app/plugins/datasource/graphite/migrations.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/graphite/parser.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
|
@ -0,0 +1,56 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { QueryEditorProps } from '@grafana/data';
|
||||
import { InlineFormLabel, Input, TagsInput } from '@grafana/ui';
|
||||
|
||||
import { GraphiteDatasource } from '../datasource';
|
||||
import { GraphiteQuery, GraphiteOptions } from '../types';
|
||||
|
||||
export const AnnotationEditor = (props: QueryEditorProps<GraphiteDatasource, GraphiteQuery, GraphiteOptions>) => {
|
||||
const { query, onChange } = props;
|
||||
const [target, setTarget] = useState<string>(query.target ?? '');
|
||||
const [tags, setTags] = useState<string[]>(query.tags ?? []);
|
||||
const updateValue = <K extends keyof GraphiteQuery, V extends GraphiteQuery[K]>(key: K, val: V) => {
|
||||
if (key === 'tags') {
|
||||
onChange({
|
||||
...query,
|
||||
[key]: val,
|
||||
fromAnnotations: true,
|
||||
queryType: key,
|
||||
});
|
||||
} else {
|
||||
onChange({
|
||||
...query,
|
||||
[key]: val,
|
||||
fromAnnotations: true,
|
||||
textEditor: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onTagsChange = (tagsInput: string[]) => {
|
||||
setTags(tagsInput);
|
||||
updateValue('tags', tagsInput);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={12}>Graphite Query</InlineFormLabel>
|
||||
<Input
|
||||
value={target}
|
||||
onChange={(e) => setTarget(e.currentTarget.value || '')}
|
||||
onBlur={() => updateValue('target', target)}
|
||||
placeholder="Example: statsd.application.counters.*.count"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h5 className="section-heading">Or</h5>
|
||||
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={12}>Graphite events tags</InlineFormLabel>
|
||||
<TagsInput id="tags-input" tags={tags} onChange={onTagsChange} placeholder="Example: event_tag" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -2,7 +2,7 @@ import { isArray } from 'lodash';
|
||||
import { of } from 'rxjs';
|
||||
import { createFetchResponse } from 'test/helpers/createFetchResponse';
|
||||
|
||||
import { AbstractLabelMatcher, AbstractLabelOperator, dateTime, getFrameDisplayName } from '@grafana/data';
|
||||
import { AbstractLabelMatcher, AbstractLabelOperator, getFrameDisplayName, dateTime } from '@grafana/data';
|
||||
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
@ -196,13 +196,21 @@ describe('graphiteDatasource', () => {
|
||||
});
|
||||
|
||||
const options = {
|
||||
annotation: {
|
||||
tags: 'tag1',
|
||||
},
|
||||
targets: [
|
||||
{
|
||||
fromAnnotations: true,
|
||||
tags: ['tag1'],
|
||||
queryType: 'tags',
|
||||
},
|
||||
],
|
||||
|
||||
range: {
|
||||
from: dateTime(1432288354),
|
||||
to: dateTime(1432288401),
|
||||
raw: { from: 'now-24h', to: 'now' },
|
||||
from: '2022-06-06T07:03:03.109Z',
|
||||
to: '2022-06-07T07:03:03.109Z',
|
||||
raw: {
|
||||
from: '2022-06-06T07:03:03.109Z',
|
||||
to: '2022-06-07T07:03:03.109Z',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -221,7 +229,7 @@ describe('graphiteDatasource', () => {
|
||||
fetchMock.mockImplementation((options: any) => {
|
||||
return of(createFetchResponse(response));
|
||||
});
|
||||
await ctx.ds.annotationQuery(options).then((data: any) => {
|
||||
await ctx.ds.annotationEvents(options.range, options.targets[0]).then((data: any) => {
|
||||
results = data;
|
||||
});
|
||||
});
|
||||
@ -250,7 +258,7 @@ describe('graphiteDatasource', () => {
|
||||
return of(createFetchResponse(response));
|
||||
});
|
||||
|
||||
await ctx.ds.annotationQuery(options).then((data: any) => {
|
||||
await ctx.ds.annotationEvents(options.range, options.targets[0]).then((data: any) => {
|
||||
results = data;
|
||||
});
|
||||
});
|
||||
@ -267,7 +275,7 @@ describe('graphiteDatasource', () => {
|
||||
fetchMock.mockImplementation((options: any) => {
|
||||
return of(createFetchResponse('zzzzzzz'));
|
||||
});
|
||||
await ctx.ds.annotationQuery(options).then((data: any) => {
|
||||
await ctx.ds.annotationEvents(options.range, options.targets[0]).then((data: any) => {
|
||||
results = data;
|
||||
});
|
||||
expect(results).toEqual([]);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { each, indexOf, isArray, isString, map as _map } from 'lodash';
|
||||
import { lastValueFrom, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs';
|
||||
import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
@ -26,15 +26,18 @@ import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/data
|
||||
|
||||
import { getSearchFilterScopedVar } from '../../../features/variables/utils';
|
||||
|
||||
import { AnnotationEditor } from './components/AnnotationsEditor';
|
||||
import { convertToGraphiteQueryObject } from './components/helpers';
|
||||
import gfunc, { FuncDefs, FuncInstance } from './gfunc';
|
||||
import GraphiteQueryModel from './graphite_query';
|
||||
import { prepareAnnotation } from './migrations';
|
||||
// Types
|
||||
import {
|
||||
GraphiteLokiMapping,
|
||||
GraphiteMetricLokiMatcher,
|
||||
GraphiteOptions,
|
||||
GraphiteQuery,
|
||||
GraphiteQueryRequest,
|
||||
GraphiteQueryImportConfiguration,
|
||||
GraphiteQueryType,
|
||||
GraphiteType,
|
||||
@ -97,6 +100,10 @@ export class GraphiteDatasource
|
||||
this.funcDefs = null;
|
||||
this.funcDefsPromise = null;
|
||||
this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
this.annotations = {
|
||||
QueryEditor: AnnotationEditor,
|
||||
prepareAnnotation,
|
||||
};
|
||||
}
|
||||
|
||||
getQueryOptionsInfo() {
|
||||
@ -182,40 +189,62 @@ export class GraphiteDatasource
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<GraphiteQuery>): Observable<DataQueryResponse> {
|
||||
const graphOptions = {
|
||||
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 ?? 'json',
|
||||
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
};
|
||||
const streams: Array<Observable<DataQueryResponse>> = [];
|
||||
|
||||
const params = this.buildGraphiteParams(graphOptions, options.scopedVars);
|
||||
if (params.length === 0) {
|
||||
for (const target of options.targets) {
|
||||
// hiding target is handled in buildGraphiteParams
|
||||
if (target.fromAnnotations) {
|
||||
streams.push(
|
||||
new Observable((subscriber) => {
|
||||
this.annotationEvents(options.range, target)
|
||||
.then((events) => subscriber.next({ data: [toDataFrame(events)] }))
|
||||
.catch((ex) => subscriber.error(new Error(ex)))
|
||||
.finally(() => subscriber.complete());
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// handle the queries here
|
||||
const graphOptions = {
|
||||
from: this.translateTime(options.range.from, false, options.timezone),
|
||||
until: this.translateTime(options.range.to, true, options.timezone),
|
||||
targets: options.targets,
|
||||
format: (options as GraphiteQueryRequest).format,
|
||||
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
};
|
||||
|
||||
const params = this.buildGraphiteParams(graphOptions, options.scopedVars);
|
||||
if (params.length === 0) {
|
||||
return of({ data: [] });
|
||||
}
|
||||
|
||||
if (this.isMetricTank) {
|
||||
params.push('meta=true');
|
||||
}
|
||||
|
||||
const httpOptions: any = {
|
||||
method: 'POST',
|
||||
url: '/render',
|
||||
data: params.join('&'),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
};
|
||||
|
||||
this.addTracingHeaders(httpOptions, options);
|
||||
|
||||
if (options.panelId) {
|
||||
httpOptions.requestId = this.name + '.panelId.' + options.panelId;
|
||||
}
|
||||
|
||||
streams.push(this.doGraphiteRequest(httpOptions).pipe(map(this.convertResponseToDataFrames)));
|
||||
}
|
||||
}
|
||||
|
||||
if (streams.length === 0) {
|
||||
return of({ data: [] });
|
||||
}
|
||||
|
||||
if (this.isMetricTank) {
|
||||
params.push('meta=true');
|
||||
}
|
||||
|
||||
const httpOptions: any = {
|
||||
method: 'POST',
|
||||
url: '/render',
|
||||
data: params.join('&'),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
};
|
||||
|
||||
this.addTracingHeaders(httpOptions, options);
|
||||
|
||||
if (options.panelId) {
|
||||
httpOptions.requestId = this.name + '.panelId.' + options.panelId;
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest(httpOptions).pipe(map(this.convertResponseToDataFrames));
|
||||
return merge(...streams);
|
||||
}
|
||||
|
||||
addTracingHeaders(httpOptions: { headers: any }, options: { dashboardId?: number; panelId?: number }) {
|
||||
@ -330,13 +359,13 @@ export class GraphiteDatasource
|
||||
return expandedQueries;
|
||||
}
|
||||
|
||||
annotationQuery(options: any) {
|
||||
// Graphite metric as annotation
|
||||
if (options.annotation.target) {
|
||||
const target = this.templateSrv.replace(options.annotation.target, {}, 'glob');
|
||||
annotationEvents(range: any, target: any) {
|
||||
if (target.target) {
|
||||
// Graphite query as target as annotation
|
||||
const targetAnnotation = this.templateSrv.replace(target.target, {}, 'glob');
|
||||
const graphiteQuery = {
|
||||
range: options.range,
|
||||
targets: [{ target: target }],
|
||||
range: range,
|
||||
targets: [{ target: targetAnnotation }],
|
||||
format: 'json',
|
||||
maxDataPoints: 100,
|
||||
} as unknown as DataQueryRequest<GraphiteQuery>;
|
||||
@ -358,7 +387,7 @@ export class GraphiteDatasource
|
||||
}
|
||||
|
||||
list.push({
|
||||
annotation: options.annotation,
|
||||
annotation: target,
|
||||
time,
|
||||
title: target.name,
|
||||
});
|
||||
@ -370,9 +399,9 @@ export class GraphiteDatasource
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Graphite event as annotation
|
||||
const tags = this.templateSrv.replace(options.annotation.tags);
|
||||
return this.events({ range: options.range, tags: tags }).then((results: any) => {
|
||||
// Graphite event/tag as annotation
|
||||
const tags = this.templateSrv.replace(target.tags?.join(' '));
|
||||
return this.events({ range: range, tags: tags }).then((results: any) => {
|
||||
const list = [];
|
||||
if (!isArray(results.data)) {
|
||||
console.error(`Unable to get annotations from ${results.url}.`);
|
||||
@ -387,7 +416,7 @@ export class GraphiteDatasource
|
||||
}
|
||||
|
||||
list.push({
|
||||
annotation: options.annotation,
|
||||
annotation: target,
|
||||
time: e.when * 1000,
|
||||
title: e.what,
|
||||
tags: tags,
|
||||
|
39
public/app/plugins/datasource/graphite/migrations.ts
Normal file
39
public/app/plugins/datasource/graphite/migrations.ts
Normal file
@ -0,0 +1,39 @@
|
||||
type LegacyAnnotation = {
|
||||
target?: string;
|
||||
tags?: string;
|
||||
};
|
||||
|
||||
// this becomes the target in the migrated annotations
|
||||
const migrateLegacyAnnotation = (json: LegacyAnnotation) => {
|
||||
// return the target annotation
|
||||
if (typeof json.target === 'string' && json.target) {
|
||||
return {
|
||||
fromAnnotations: true,
|
||||
target: json.target,
|
||||
textEditor: true,
|
||||
};
|
||||
}
|
||||
|
||||
// return the tags annotation
|
||||
return {
|
||||
queryType: 'tags',
|
||||
tags: (json.tags || '').split(' '),
|
||||
fromAnnotations: true,
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-ignore-next-line
|
||||
export const prepareAnnotation = (json: any) => {
|
||||
// annotation attributes are either 'tags' or 'target'(a graphite query string)
|
||||
// because the new annotations will also have a target attribute, {}
|
||||
// we need to handle the ambiguous 'target' when migrating legacy annotations
|
||||
// so, to migrate legacy annotations
|
||||
// we check that target is a string
|
||||
// or
|
||||
// there is a tags attribute with no target
|
||||
const resultingTarget = json.target && typeof json.target !== 'string' ? json.target : migrateLegacyAnnotation(json);
|
||||
|
||||
json.target = resultingTarget;
|
||||
|
||||
return json;
|
||||
};
|
@ -6,13 +6,8 @@ import { MetricTankMetaInspector } from './components/MetricTankMetaInspector';
|
||||
import { ConfigEditor } from './configuration/ConfigEditor';
|
||||
import { GraphiteDatasource } from './datasource';
|
||||
|
||||
class AnnotationsQueryCtrl {
|
||||
static templateUrl = 'partials/annotations.editor.html';
|
||||
}
|
||||
|
||||
export const plugin = new DataSourcePlugin(GraphiteDatasource)
|
||||
.setQueryEditor(GraphiteQueryEditor)
|
||||
.setConfigEditor(ConfigEditor)
|
||||
.setVariableQueryEditor(GraphiteVariableEditor)
|
||||
.setMetadataInspector(MetricTankMetaInspector)
|
||||
.setAnnotationQueryCtrl(AnnotationsQueryCtrl);
|
||||
.setMetadataInspector(MetricTankMetaInspector);
|
||||
|
@ -1,13 +0,0 @@
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-12">Graphite query</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.target' placeholder="Example: statsd.application.counters.*.count"></input>
|
||||
</div>
|
||||
|
||||
<h5 class="section-heading">Or</h5>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-12">Graphite events tags</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.tags' placeholder="Example: event_tag_name"></input>
|
||||
</div>
|
||||
</div>
|
@ -1,4 +1,4 @@
|
||||
import { DataQuery, DataSourceJsonData, TimeRange } from '@grafana/data';
|
||||
import { DataQuery, DataQueryRequest, DataSourceJsonData, TimeRange } from '@grafana/data';
|
||||
|
||||
import { TemplateSrv } from '../../../features/templating/template_srv';
|
||||
|
||||
@ -11,7 +11,11 @@ export enum GraphiteQueryType {
|
||||
}
|
||||
|
||||
export interface GraphiteQuery extends DataQuery {
|
||||
queryType?: string;
|
||||
textEditor?: boolean;
|
||||
target?: string;
|
||||
tags?: string[];
|
||||
fromAnnotations?: boolean;
|
||||
}
|
||||
|
||||
export interface GraphiteOptions extends DataSourceJsonData {
|
||||
@ -93,3 +97,7 @@ export type GraphiteQueryEditorDependencies = {
|
||||
// schedule onChange/onRunQuery after the reducer actions finishes
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
export interface GraphiteQueryRequest extends DataQueryRequest {
|
||||
format: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user