Commit ae385983 by Ryan McKinley Committed by GitHub

InfluxDB: support flux editor for template queries (#27370)

parent b867050c
......@@ -9,4 +9,4 @@ export * from './types';
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
export { reportMetaAnalytics } from './utils/analytics';
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,
TableData,
toDataFrame,
DataFrame,
MetricFindValue,
FieldType,
} from '@grafana/data';
interface DataResponse {
......@@ -115,3 +118,22 @@ export function toDataQueryError(err: any): DataQueryError {
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 == "" {
model.Options.Organization = dsInfo.JsonData.Get("organization").MustString("")
}
startTime, err := timeRange.ParseFrom()
if err != nil {
return nil, err
if err != nil && timeRange.From != "" {
return nil, fmt.Errorf("error reading startTime: %w", err)
}
endTime, err := timeRange.ParseTo()
if err != nil {
return nil, err
if err != nil && timeRange.To != "" {
return nil, fmt.Errorf("error reading endTime: %w", err)
}
// Copy directly from the well typed query
......
......@@ -60,7 +60,7 @@ v1.measurements(bucket: v.bucket)`,
label: 'Schema Exploration: (fields)',
description: 'Return every possible key in a single table',
value: `from(bucket: v.bucket)
|> range(start: v.timeRangeStart, stop:timeRangeStop)
|> range(start: v.timeRangeStart, stop:v.timeRangeStop)
|> keys()
|> keep(columns: ["_value"])
|> group()
......
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,
LoadingState,
QueryResultMeta,
MetricFindValue,
AnnotationQueryRequest,
AnnotationEvent,
} from '@grafana/data';
import { v4 as uuidv4 } from 'uuid';
import InfluxSeries from './influx_series';
......@@ -16,7 +19,7 @@ import InfluxQueryModel from './influx_query_model';
import ResponseParser from './response_parser';
import { InfluxQueryBuilder } from './query_builder';
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';
export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery, InfluxOptions> {
......@@ -31,7 +34,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
interval: any;
responseParser: any;
httpMode: string;
is2x: boolean;
isFlux: boolean;
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>) {
super(instanceSettings);
......@@ -51,11 +54,11 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
this.interval = settingsData.timeInterval;
this.httpMode = settingsData.httpMode || 'GET';
this.responseParser = new ResponseParser();
this.is2x = settingsData.version === InfluxVersion.Flux;
this.isFlux = settingsData.version === InfluxVersion.Flux;
}
query(request: DataQueryRequest<InfluxQuery>): Observable<DataQueryResponse> {
if (this.is2x) {
if (this.isFlux) {
return super.query(request);
}
......@@ -64,7 +67,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
}
getQueryDisplayText(query: InfluxQuery) {
if (this.is2x) {
if (this.isFlux) {
return query.query;
}
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) {
return Promise.reject({
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);
query = getTemplateSrv().replace(query, undefined, 'regex');
......@@ -256,7 +266,26 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
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');
return this._seriesQuery(interpolated, options).then(resp => {
......@@ -308,7 +337,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
}
testDatasource() {
if (this.is2x) {
if (this.isFlux) {
// TODO: eventually use the real /health endpoint
const request: DataQueryRequest<InfluxQuery> = {
targets: [{ refId: 'test', query: 'buckets()' }],
......
......@@ -3,6 +3,7 @@ import { InfluxQueryCtrl } from './query_ctrl';
import InfluxStartPage from './components/InfluxStartPage';
import { DataSourcePlugin } from '@grafana/data';
import ConfigEditor from './components/ConfigEditor';
import VariableQueryEditor from './components/VariableQueryEditor';
// This adds a directive that is used in the query editor
import './components/FluxQueryEditor';
......@@ -15,4 +16,5 @@ export const plugin = new DataSourcePlugin(InfluxDatasource)
.setConfigEditor(ConfigEditor)
.setQueryCtrl(InfluxQueryCtrl)
.setAnnotationQueryCtrl(InfluxAnnotationsQueryCtrl)
.setVariableQueryEditor(VariableQueryEditor)
.setExploreStartPage(InfluxStartPage);
<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
target="ctrl.target"
change="ctrl.onChange"
......@@ -7,7 +7,7 @@
></flux-query-editor>
</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 class="gf-form">
<textarea
......
......@@ -256,7 +256,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
// Only valid for InfluxQL queries
toggleEditorMode() {
if (this.datasource.is2x) {
if (this.datasource.isFlux) {
return; // nothing
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment