mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
InfluxDB: support flux editor for template queries (#27370)
This commit is contained in:
@@ -9,4 +9,4 @@ export * from './types';
|
|||||||
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
|
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
|
||||||
export { reportMetaAnalytics } from './utils/analytics';
|
export { reportMetaAnalytics } from './utils/analytics';
|
||||||
export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend';
|
export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend';
|
||||||
export { toDataQueryError, toDataQueryResponse } from './utils/queryResponse';
|
export { toDataQueryError, toDataQueryResponse, frameToMetricFindValue } from './utils/queryResponse';
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import {
|
|||||||
TimeSeries,
|
TimeSeries,
|
||||||
TableData,
|
TableData,
|
||||||
toDataFrame,
|
toDataFrame,
|
||||||
|
DataFrame,
|
||||||
|
MetricFindValue,
|
||||||
|
FieldType,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
interface DataResponse {
|
interface DataResponse {
|
||||||
@@ -115,3 +118,22 @@ export function toDataQueryError(err: any): DataQueryError {
|
|||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Return the first string or non-time field as the value */
|
||||||
|
export function frameToMetricFindValue(frame: DataFrame): MetricFindValue[] {
|
||||||
|
if (!frame || !frame.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const values: MetricFindValue[] = [];
|
||||||
|
let field = frame.fields.find(f => f.type === FieldType.string);
|
||||||
|
if (!field) {
|
||||||
|
field = frame.fields.find(f => f.type !== FieldType.time);
|
||||||
|
}
|
||||||
|
if (field) {
|
||||||
|
for (let i = 0; i < field.values.length; i++) {
|
||||||
|
values.push({ text: '' + field.values.get(i) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,14 +66,15 @@ func getQueryModelTSDB(query *tsdb.Query, timeRange *tsdb.TimeRange, dsInfo *mod
|
|||||||
if model.Options.Organization == "" {
|
if model.Options.Organization == "" {
|
||||||
model.Options.Organization = dsInfo.JsonData.Get("organization").MustString("")
|
model.Options.Organization = dsInfo.JsonData.Get("organization").MustString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime, err := timeRange.ParseFrom()
|
startTime, err := timeRange.ParseFrom()
|
||||||
if err != nil {
|
if err != nil && timeRange.From != "" {
|
||||||
return nil, err
|
return nil, fmt.Errorf("error reading startTime: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endTime, err := timeRange.ParseTo()
|
endTime, err := timeRange.ParseTo()
|
||||||
if err != nil {
|
if err != nil && timeRange.To != "" {
|
||||||
return nil, err
|
return nil, fmt.Errorf("error reading endTime: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy directly from the well typed query
|
// Copy directly from the well typed query
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ v1.measurements(bucket: v.bucket)`,
|
|||||||
label: 'Schema Exploration: (fields)',
|
label: 'Schema Exploration: (fields)',
|
||||||
description: 'Return every possible key in a single table',
|
description: 'Return every possible key in a single table',
|
||||||
value: `from(bucket: v.bucket)
|
value: `from(bucket: v.bucket)
|
||||||
|> range(start: v.timeRangeStart, stop:timeRangeStop)
|
|> range(start: v.timeRangeStart, stop:v.timeRangeStop)
|
||||||
|> keys()
|
|> keys()
|
||||||
|> keep(columns: ["_value"])
|
|> keep(columns: ["_value"])
|
||||||
|> group()
|
|> group()
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import InfluxDatasource from '../datasource';
|
||||||
|
import { InlineFormLabel, TextArea } from '@grafana/ui';
|
||||||
|
import { FluxQueryEditor } from './FluxQueryEditor';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
query: string; // before flux, it was always a string
|
||||||
|
onChange: (query?: string) => void;
|
||||||
|
datasource: InfluxDatasource;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class VariableQueryEditor extends PureComponent<Props> {
|
||||||
|
onRefresh = () => {
|
||||||
|
// noop
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { query, datasource, onChange } = this.props;
|
||||||
|
if (datasource.isFlux) {
|
||||||
|
return (
|
||||||
|
<FluxQueryEditor
|
||||||
|
target={{
|
||||||
|
refId: 'A',
|
||||||
|
query,
|
||||||
|
}}
|
||||||
|
refresh={this.onRefresh}
|
||||||
|
change={v => onChange(v.query)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<InlineFormLabel width={10}>Query</InlineFormLabel>
|
||||||
|
<div className="gf-form-inline gf-form--grow">
|
||||||
|
<TextArea
|
||||||
|
value={query || ''}
|
||||||
|
placeholder="metric name or tags query"
|
||||||
|
rows={1}
|
||||||
|
className="gf-form-input"
|
||||||
|
onChange={e => onChange(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ import {
|
|||||||
dateTime,
|
dateTime,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
QueryResultMeta,
|
QueryResultMeta,
|
||||||
|
MetricFindValue,
|
||||||
|
AnnotationQueryRequest,
|
||||||
|
AnnotationEvent,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import InfluxSeries from './influx_series';
|
import InfluxSeries from './influx_series';
|
||||||
@@ -16,7 +19,7 @@ import InfluxQueryModel from './influx_query_model';
|
|||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
import { InfluxQueryBuilder } from './query_builder';
|
import { InfluxQueryBuilder } from './query_builder';
|
||||||
import { InfluxQuery, InfluxOptions, InfluxVersion } from './types';
|
import { InfluxQuery, InfluxOptions, InfluxVersion } from './types';
|
||||||
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend, frameToMetricFindValue } from '@grafana/runtime';
|
||||||
import { Observable, from } from 'rxjs';
|
import { Observable, from } from 'rxjs';
|
||||||
|
|
||||||
export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery, InfluxOptions> {
|
export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery, InfluxOptions> {
|
||||||
@@ -31,7 +34,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
|||||||
interval: any;
|
interval: any;
|
||||||
responseParser: any;
|
responseParser: any;
|
||||||
httpMode: string;
|
httpMode: string;
|
||||||
is2x: boolean;
|
isFlux: boolean;
|
||||||
|
|
||||||
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>) {
|
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>) {
|
||||||
super(instanceSettings);
|
super(instanceSettings);
|
||||||
@@ -51,11 +54,11 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
|||||||
this.interval = settingsData.timeInterval;
|
this.interval = settingsData.timeInterval;
|
||||||
this.httpMode = settingsData.httpMode || 'GET';
|
this.httpMode = settingsData.httpMode || 'GET';
|
||||||
this.responseParser = new ResponseParser();
|
this.responseParser = new ResponseParser();
|
||||||
this.is2x = settingsData.version === InfluxVersion.Flux;
|
this.isFlux = settingsData.version === InfluxVersion.Flux;
|
||||||
}
|
}
|
||||||
|
|
||||||
query(request: DataQueryRequest<InfluxQuery>): Observable<DataQueryResponse> {
|
query(request: DataQueryRequest<InfluxQuery>): Observable<DataQueryResponse> {
|
||||||
if (this.is2x) {
|
if (this.isFlux) {
|
||||||
return super.query(request);
|
return super.query(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +67,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
|||||||
}
|
}
|
||||||
|
|
||||||
getQueryDisplayText(query: InfluxQuery) {
|
getQueryDisplayText(query: InfluxQuery) {
|
||||||
if (this.is2x) {
|
if (this.isFlux) {
|
||||||
return query.query;
|
return query.query;
|
||||||
}
|
}
|
||||||
return new InfluxQueryModel(query).render(false);
|
return new InfluxQueryModel(query).render(false);
|
||||||
@@ -177,14 +180,21 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationQuery(options: any) {
|
async annotationQuery(options: AnnotationQueryRequest<any>): Promise<AnnotationEvent[]> {
|
||||||
|
if (this.isFlux) {
|
||||||
|
return Promise.reject({
|
||||||
|
message: 'Annotations are not yet supported with flux queries',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfluxQL puts a query string on the annotation
|
||||||
if (!options.annotation.query) {
|
if (!options.annotation.query) {
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
message: 'Query missing in annotation definition',
|
message: 'Query missing in annotation definition',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw, timezone: options.timezone });
|
const timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw, timezone: options.dashboard.timezone });
|
||||||
let query = options.annotation.query.replace('$timeFilter', timeFilter);
|
let query = options.annotation.query.replace('$timeFilter', timeFilter);
|
||||||
query = getTemplateSrv().replace(query, undefined, 'regex');
|
query = getTemplateSrv().replace(query, undefined, 'regex');
|
||||||
|
|
||||||
@@ -256,7 +266,26 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
|||||||
return expandedQueries;
|
return expandedQueries;
|
||||||
}
|
}
|
||||||
|
|
||||||
metricFindQuery(query: string, options?: any) {
|
async metricFindQuery(query: string, options?: any): Promise<MetricFindValue[]> {
|
||||||
|
if (this.isFlux) {
|
||||||
|
const target: InfluxQuery = {
|
||||||
|
refId: 'metricFindQuery',
|
||||||
|
query,
|
||||||
|
};
|
||||||
|
return super
|
||||||
|
.query({
|
||||||
|
...options, // includes 'range'
|
||||||
|
targets: [target],
|
||||||
|
} as DataQueryRequest)
|
||||||
|
.toPromise()
|
||||||
|
.then(rsp => {
|
||||||
|
if (rsp.data?.length) {
|
||||||
|
return frameToMetricFindValue(rsp.data[0]);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const interpolated = getTemplateSrv().replace(query, undefined, 'regex');
|
const interpolated = getTemplateSrv().replace(query, undefined, 'regex');
|
||||||
|
|
||||||
return this._seriesQuery(interpolated, options).then(resp => {
|
return this._seriesQuery(interpolated, options).then(resp => {
|
||||||
@@ -308,7 +337,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
|||||||
}
|
}
|
||||||
|
|
||||||
testDatasource() {
|
testDatasource() {
|
||||||
if (this.is2x) {
|
if (this.isFlux) {
|
||||||
// TODO: eventually use the real /health endpoint
|
// TODO: eventually use the real /health endpoint
|
||||||
const request: DataQueryRequest<InfluxQuery> = {
|
const request: DataQueryRequest<InfluxQuery> = {
|
||||||
targets: [{ refId: 'test', query: 'buckets()' }],
|
targets: [{ refId: 'test', query: 'buckets()' }],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { InfluxQueryCtrl } from './query_ctrl';
|
|||||||
import InfluxStartPage from './components/InfluxStartPage';
|
import InfluxStartPage from './components/InfluxStartPage';
|
||||||
import { DataSourcePlugin } from '@grafana/data';
|
import { DataSourcePlugin } from '@grafana/data';
|
||||||
import ConfigEditor from './components/ConfigEditor';
|
import ConfigEditor from './components/ConfigEditor';
|
||||||
|
import VariableQueryEditor from './components/VariableQueryEditor';
|
||||||
|
|
||||||
// This adds a directive that is used in the query editor
|
// This adds a directive that is used in the query editor
|
||||||
import './components/FluxQueryEditor';
|
import './components/FluxQueryEditor';
|
||||||
@@ -15,4 +16,5 @@ export const plugin = new DataSourcePlugin(InfluxDatasource)
|
|||||||
.setConfigEditor(ConfigEditor)
|
.setConfigEditor(ConfigEditor)
|
||||||
.setQueryCtrl(InfluxQueryCtrl)
|
.setQueryCtrl(InfluxQueryCtrl)
|
||||||
.setAnnotationQueryCtrl(InfluxAnnotationsQueryCtrl)
|
.setAnnotationQueryCtrl(InfluxAnnotationsQueryCtrl)
|
||||||
|
.setVariableQueryEditor(VariableQueryEditor)
|
||||||
.setExploreStartPage(InfluxStartPage);
|
.setExploreStartPage(InfluxStartPage);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
<query-editor-row ng-if="ctrl.datasource.is2x" query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
|
<query-editor-row ng-if="ctrl.datasource.isFlux" query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
|
||||||
<flux-query-editor
|
<flux-query-editor
|
||||||
target="ctrl.target"
|
target="ctrl.target"
|
||||||
change="ctrl.onChange"
|
change="ctrl.onChange"
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
></flux-query-editor>
|
></flux-query-editor>
|
||||||
</query-editor-row>
|
</query-editor-row>
|
||||||
|
|
||||||
<query-editor-row ng-if="!ctrl.datasource.is2x" query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
|
<query-editor-row ng-if="!ctrl.datasource.isFlux" query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
|
||||||
<div ng-if="ctrl.target.rawQuery">
|
<div ng-if="ctrl.target.rawQuery">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<textarea
|
<textarea
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
|||||||
|
|
||||||
// Only valid for InfluxQL queries
|
// Only valid for InfluxQL queries
|
||||||
toggleEditorMode() {
|
toggleEditorMode() {
|
||||||
if (this.datasource.is2x) {
|
if (this.datasource.isFlux) {
|
||||||
return; // nothing
|
return; // nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user