mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
InfluxDB: different config UI for 1x vs 2x (#25723)
This commit is contained in:
parent
0797fe88a1
commit
8d1ed33e20
@ -57,7 +57,7 @@ export const SecretFormField: FunctionComponent<Props> = ({
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
className={cx(`gf-form-input width-${inputWidth! - 2}`, styles.noRadiusInput)}
|
||||
className={cx(`gf-form-input width-${inputWidth}`, styles.noRadiusInput)}
|
||||
disabled={true}
|
||||
value="configured"
|
||||
{...omit(inputProps, 'value')}
|
||||
|
@ -43,36 +43,18 @@ func init() {
|
||||
tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxDBExecutor)
|
||||
}
|
||||
|
||||
func AllFlux(queries *tsdb.TsdbQuery) (bool, error) {
|
||||
var hasFlux bool
|
||||
var allFlux bool
|
||||
for i, q := range queries.Queries {
|
||||
qType := q.Model.Get("queryType").MustString("")
|
||||
if qType == "Flux" {
|
||||
hasFlux = true
|
||||
if i == 0 && hasFlux {
|
||||
allFlux = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if allFlux && qType != "Flux" {
|
||||
return true, fmt.Errorf("when using flux, all queries must be a flux query")
|
||||
}
|
||||
}
|
||||
return allFlux, nil
|
||||
}
|
||||
|
||||
func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
result := &tsdb.Response{}
|
||||
allFlux, err := AllFlux(tsdbQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if allFlux {
|
||||
glog.Info("query", "q", tsdbQuery.Queries)
|
||||
|
||||
version := dsInfo.JsonData.Get("version").MustString("")
|
||||
if version == "Flux" {
|
||||
return flux.Query(ctx, dsInfo, tsdbQuery)
|
||||
}
|
||||
|
||||
// NOTE: the following path is currently only called from alerting queries
|
||||
// In dashboards, the request runs through proxy and are managed in the frontend
|
||||
|
||||
query, err := e.getQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -120,6 +102,7 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
|
||||
return nil, response.Err
|
||||
}
|
||||
|
||||
result := &tsdb.Response{}
|
||||
result.Results = make(map[string]*tsdb.QueryResult)
|
||||
result.Results["A"] = e.ResponseParser.Parse(&response, query)
|
||||
|
||||
|
@ -10,41 +10,179 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { DataSourceHttpSettings, InlineFormLabel, LegacyForms } from '@grafana/ui';
|
||||
const { Select, Input, SecretFormField } = LegacyForms;
|
||||
import { InfluxOptions, InfluxSecureJsonData } from '../types';
|
||||
import { InfluxOptions, InfluxSecureJsonData, InfluxVersion } from '../types';
|
||||
|
||||
const httpModes = [
|
||||
{ label: 'GET', value: 'GET' },
|
||||
{ label: 'POST', value: 'POST' },
|
||||
] as SelectableValue[];
|
||||
|
||||
const versions = [
|
||||
{
|
||||
label: 'InfluxQL',
|
||||
value: InfluxVersion.InfluxQL,
|
||||
description: 'The InfluxDB SQL-like query language. Supported in InfluxDB 1.x',
|
||||
},
|
||||
{
|
||||
label: 'Flux',
|
||||
value: InfluxVersion.Flux,
|
||||
description: 'Advanced data scripting and query language. Supported in InfluxDB 2.x and 1.8+ (beta)',
|
||||
},
|
||||
] as Array<SelectableValue<InfluxVersion>>;
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<InfluxOptions>;
|
||||
|
||||
export class ConfigEditor extends PureComponent<Props> {
|
||||
// 1x
|
||||
onResetPassword = () => {
|
||||
updateDatasourcePluginResetOption(this.props, 'password');
|
||||
};
|
||||
|
||||
// 2x
|
||||
onResetToken = () => {
|
||||
updateDatasourcePluginResetOption(this.props, 'token');
|
||||
};
|
||||
|
||||
onToggleFlux = (event: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
onVersionChanged = (selected: SelectableValue<InfluxVersion>) => {
|
||||
const { options, onOptionsChange } = this.props;
|
||||
onOptionsChange({
|
||||
|
||||
const copy = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
enableFlux: !options.jsonData.enableFlux,
|
||||
version: selected.value,
|
||||
},
|
||||
};
|
||||
if (selected.value === InfluxVersion.Flux) {
|
||||
copy.access = 'proxy';
|
||||
copy.basicAuth = true;
|
||||
copy.jsonData.httpMode = 'POST';
|
||||
|
||||
// Remove old 1x configs
|
||||
delete copy.user;
|
||||
delete copy.database;
|
||||
}
|
||||
onOptionsChange(copy);
|
||||
};
|
||||
|
||||
onUpdateInflux2xURL = (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const { options, onOptionsChange } = this.props;
|
||||
onOptionsChange({
|
||||
...options,
|
||||
url: e.currentTarget.value,
|
||||
access: 'proxy',
|
||||
basicAuth: true,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
renderInflux2x() {
|
||||
const { options } = this.props;
|
||||
const { secureJsonFields } = options;
|
||||
const secureJsonData = (options.secureJsonData || {}) as InfluxSecureJsonData;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="gf-form-group">
|
||||
<div className="width-30 grafana-info-box">
|
||||
<h5>Support for flux in Grafana is currently in beta</h5>
|
||||
<p>
|
||||
Please report any issues to: <br />
|
||||
<a href="https://github.com/grafana/grafana/issues/new/choose">
|
||||
https://github.com/grafana/grafana/issues
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<h3 className="page-heading">Connection</h3>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
className="width-10"
|
||||
tooltip="This URL needs to be accessible from the grafana backend/server."
|
||||
>
|
||||
URL
|
||||
</InlineFormLabel>
|
||||
<div className="width-20">
|
||||
<Input
|
||||
className="width-20"
|
||||
value={options.url || ''}
|
||||
placeholder="http://localhost:9999/api/v2"
|
||||
onChange={this.onUpdateInflux2xURL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-10">Organization</InlineFormLabel>
|
||||
<div className="width-10">
|
||||
<Input
|
||||
className="width-20"
|
||||
value={options.jsonData.organization || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'organization')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<SecretFormField
|
||||
isConfigured={(secureJsonFields && secureJsonFields.token) as boolean}
|
||||
value={secureJsonData.token || ''}
|
||||
label="Token"
|
||||
labelWidth={10}
|
||||
inputWidth={20}
|
||||
onReset={this.onResetToken}
|
||||
onChange={onUpdateDatasourceSecureJsonDataOption(this.props, 'token')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-10">Default Bucket</InlineFormLabel>
|
||||
<div className="width-10">
|
||||
<Input
|
||||
className="width-20"
|
||||
placeholder="default bucket"
|
||||
value={options.jsonData.defaultBucket || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'defaultBucket')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
className="width-10"
|
||||
tooltip="A lower limit for the auto group by time interval. Recommended to be set to write frequency,
|
||||
for example 1m if your data is written every minute."
|
||||
>
|
||||
Min time interval
|
||||
</InlineFormLabel>
|
||||
<div className="width-10">
|
||||
<Input
|
||||
className="width-10"
|
||||
placeholder="10s"
|
||||
value={options.jsonData.timeInterval || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'timeInterval')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderInflux1x() {
|
||||
const { options, onOptionsChange } = this.props;
|
||||
const { secureJsonFields } = options;
|
||||
const secureJsonData = (options.secureJsonData || {}) as InfluxSecureJsonData;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<DataSourceHttpSettings
|
||||
showAccessOptions={true}
|
||||
dataSourceConfig={options}
|
||||
@ -54,50 +192,6 @@ export class ConfigEditor extends PureComponent<Props> {
|
||||
|
||||
<h3 className="page-heading">InfluxDB Details</h3>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form-inline">
|
||||
<LegacyForms.Switch
|
||||
label="Enable flux"
|
||||
labelClass="width-10"
|
||||
checked={options.jsonData.enableFlux || false}
|
||||
onChange={this.onToggleFlux}
|
||||
tooltip="Suport flux query endpoint"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{options.jsonData.enableFlux && (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-10">Organization</InlineFormLabel>
|
||||
<div className="width-10">
|
||||
<Input
|
||||
className="width-10"
|
||||
placeholder="enter organization"
|
||||
value={options.jsonData.organization || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'organization')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-10">Default Bucket</InlineFormLabel>
|
||||
<div className="width-10">
|
||||
<Input
|
||||
className="width-10"
|
||||
placeholder="default bucket"
|
||||
value={options.jsonData.defaultBucket || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(this.props, 'defaultBucket')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-10">Database</InlineFormLabel>
|
||||
@ -135,19 +229,6 @@ export class ConfigEditor extends PureComponent<Props> {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<SecretFormField
|
||||
isConfigured={(secureJsonFields && secureJsonFields.token) as boolean}
|
||||
value={secureJsonData.token || ''}
|
||||
label="Token"
|
||||
labelWidth={10}
|
||||
inputWidth={20}
|
||||
onReset={this.onResetPassword}
|
||||
onChange={onUpdateDatasourceSecureJsonDataOption(this.props, 'token')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
@ -168,6 +249,7 @@ export class ConfigEditor extends PureComponent<Props> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="gf-form-group">
|
||||
<div className="grafana-info-box">
|
||||
<h5>Database Access</h5>
|
||||
@ -202,6 +284,31 @@ export class ConfigEditor extends PureComponent<Props> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="page-heading">Query Language</h3>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<Select
|
||||
className="width-30"
|
||||
value={options.jsonData.version === InfluxVersion.Flux ? versions[1] : versions[0]}
|
||||
options={versions}
|
||||
defaultValue={versions[0]}
|
||||
onChange={this.onVersionChanged}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{options.jsonData.version === InfluxVersion.Flux ? this.renderInflux2x() : this.renderInflux1x()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,142 @@
|
||||
import React, { Component } from 'react';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { InfluxQuery } from '../types';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineFormLabel, LinkButton, Select, TextArea } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
target: InfluxQuery;
|
||||
change: (target: InfluxQuery) => void;
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
const samples: Array<SelectableValue<string>> = [
|
||||
{ label: 'Show buckets', description: 'List the avaliable buckets (table)', value: 'buckets()' },
|
||||
{
|
||||
label: 'Simple query',
|
||||
description: 'filter by measurment and field',
|
||||
value: `from(bucket: "db/rp")
|
||||
|> range(start: v.timeRangeStart, stop:timeRangeStop)
|
||||
|> filter(fn: (r) =>
|
||||
r._measurement == "example-measurement" and
|
||||
r._field == "example-field"
|
||||
)`,
|
||||
},
|
||||
{
|
||||
label: 'Grouped Query',
|
||||
description: 'Group by (min/max/sum/median)',
|
||||
value: `// v.windowPeriod is a variable referring to the current optimized window period (currently: $interval)
|
||||
from(bucket: v.bucket)
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_measurement"] == "measurement1" or r["_measurement"] =~ /^.*?regex.*$/)
|
||||
|> filter(fn: (r) => r["_field"] == "field2" or r["_field"] =~ /^.*?regex.*$/)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean|median|max|count|derivative|sum)
|
||||
|> yield(name: "some-name")`,
|
||||
},
|
||||
{
|
||||
label: 'Filter by value',
|
||||
description: 'Results between a min/max',
|
||||
value: `// v.bucket, v.timeRangeStart, and v.timeRange stop are all variables supported by the flux plugin and influxdb
|
||||
from(bucket: v.bucket)
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_value"] >= 10 and r["_value"] <= 20)`,
|
||||
},
|
||||
{
|
||||
label: 'Schema Exploration: (measurements)',
|
||||
description: 'Get a list of measurement using flux',
|
||||
value: `import "influxdata/influxdb/v1"
|
||||
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: -30m)
|
||||
|> keys()
|
||||
|> keep(columns: ["_value"])
|
||||
|> group()
|
||||
|> distinct()`,
|
||||
},
|
||||
{
|
||||
label: 'Schema Exploration: (tag keys)',
|
||||
description: 'Get a list of tag keys using flux',
|
||||
value: `import "influxdata/influxdb/v1"
|
||||
v1.tagKeys(bucket: v.bucket)`,
|
||||
},
|
||||
{
|
||||
label: 'Schema Exploration: (tag values)',
|
||||
description: 'Get a list of tag values using flux',
|
||||
value: `import "influxdata/influxdb/v1"
|
||||
v1.tagValues(
|
||||
bucket: v.bucket,
|
||||
tag: "host",
|
||||
predicate: (r) => true,
|
||||
start: -1d
|
||||
)`,
|
||||
},
|
||||
];
|
||||
|
||||
export class FluxQueryEditor extends Component<Props> {
|
||||
onFluxQueryChange = (e: any) => {
|
||||
const { target, change } = this.props;
|
||||
change({ ...target, query: e.currentTarget.value });
|
||||
};
|
||||
|
||||
onFluxBlur = (e: any) => {
|
||||
this.props.refresh();
|
||||
};
|
||||
|
||||
onSampleChange = (val: SelectableValue<string>) => {
|
||||
this.props.change({
|
||||
...this.props.target,
|
||||
query: val.value!,
|
||||
});
|
||||
|
||||
// Angular HACK: Since the target does not actually change!
|
||||
this.forceUpdate();
|
||||
this.props.refresh();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { target } = this.props;
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<TextArea
|
||||
value={target.query || ''}
|
||||
onChange={this.onFluxQueryChange}
|
||||
onBlur={this.onFluxBlur}
|
||||
placeholder="Flux query"
|
||||
rows={10}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={6} tooltip="This supports queries copied from chronograph">
|
||||
Help
|
||||
</InlineFormLabel>
|
||||
<Select width={20} options={samples} placeholder="Sample Query" onChange={this.onSampleChange} />
|
||||
<LinkButton
|
||||
icon="external-link-alt"
|
||||
variant="secondary"
|
||||
target="blank"
|
||||
href="https://docs.influxdata.com/flux/latest/introduction/getting-started/"
|
||||
>
|
||||
Flux docs
|
||||
</LinkButton>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.directive('fluxQueryEditor', [
|
||||
'reactDirective',
|
||||
(reactDirective: any) => {
|
||||
return reactDirective(FluxQueryEditor, ['target', 'change']);
|
||||
},
|
||||
]);
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,19 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { dateMath, DataSourceInstanceSettings, ScopedVars, DataQueryRequest, DataQueryResponse } from '@grafana/data';
|
||||
import {
|
||||
dateMath,
|
||||
DataSourceInstanceSettings,
|
||||
ScopedVars,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
dateTime,
|
||||
LoadingState,
|
||||
} from '@grafana/data';
|
||||
import InfluxSeries from './influx_series';
|
||||
import InfluxQueryModel from './influx_query_model';
|
||||
import ResponseParser from './response_parser';
|
||||
import { InfluxQueryBuilder } from './query_builder';
|
||||
import { InfluxQuery, InfluxOptions, InfluxQueryType } from './types';
|
||||
import { InfluxQuery, InfluxOptions, InfluxVersion } from './types';
|
||||
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
||||
import { Observable, from } from 'rxjs';
|
||||
|
||||
@ -21,7 +29,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
interval: any;
|
||||
responseParser: any;
|
||||
httpMode: string;
|
||||
enableFlux: boolean;
|
||||
is2x: boolean;
|
||||
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>) {
|
||||
super(instanceSettings);
|
||||
@ -40,41 +48,11 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
this.interval = settingsData.timeInterval;
|
||||
this.httpMode = settingsData.httpMode || 'GET';
|
||||
this.responseParser = new ResponseParser();
|
||||
this.enableFlux = !!settingsData.enableFlux;
|
||||
this.is2x = settingsData.version === InfluxVersion.Flux;
|
||||
}
|
||||
|
||||
query(request: DataQueryRequest<InfluxQuery>): Observable<DataQueryResponse> {
|
||||
let hasFlux = false;
|
||||
let allFlux = true;
|
||||
|
||||
// Update the queryType fields and manage migrations
|
||||
for (const target of request.targets) {
|
||||
if (target.queryType === InfluxQueryType.Flux) {
|
||||
hasFlux = true;
|
||||
} else {
|
||||
allFlux = false;
|
||||
if (target.queryType === InfluxQueryType.Classic) {
|
||||
delete target.rawQuery;
|
||||
} else if (target.rawQuery) {
|
||||
target.queryType = InfluxQueryType.InfluxQL;
|
||||
} else if (target.queryType === InfluxQueryType.InfluxQL) {
|
||||
target.rawQuery = true; // so the old version works
|
||||
} else {
|
||||
target.queryType = InfluxQueryType.Classic; // Explicitly set it to classic
|
||||
delete target.rawQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process flux queries (data frame request)
|
||||
if (hasFlux) {
|
||||
if (!this.enableFlux) {
|
||||
throw 'Flux not enabled for this datasource';
|
||||
}
|
||||
if (!allFlux) {
|
||||
throw 'All queries must be flux';
|
||||
}
|
||||
// Calls /api/tsdb/query
|
||||
if (this.is2x) {
|
||||
return super.query(request);
|
||||
}
|
||||
|
||||
@ -311,8 +289,40 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
).join('&');
|
||||
}
|
||||
|
||||
// TODO: remove this so that everything gets sent to /healthcheck!
|
||||
testDatasource() {
|
||||
if (this.is2x) {
|
||||
// TODO: eventually use the real /health endpoint
|
||||
const request: DataQueryRequest<InfluxQuery> = {
|
||||
targets: [{ refId: 'test', query: 'buckets()' }],
|
||||
requestId: `${this.id}-health-${Date.now()}`,
|
||||
dashboardId: 0,
|
||||
panelId: 0,
|
||||
interval: '1m',
|
||||
intervalMs: 60000,
|
||||
maxDataPoints: 423,
|
||||
range: {
|
||||
from: dateTime(1000),
|
||||
to: dateTime(2000),
|
||||
},
|
||||
} as DataQueryRequest<InfluxQuery>;
|
||||
|
||||
return super
|
||||
.query(request)
|
||||
.toPromise()
|
||||
.then((res: any) => {
|
||||
const data: DataQueryResponse = res.data;
|
||||
if (data && data.state === LoadingState.Done) {
|
||||
const buckets = data.data[0].length;
|
||||
return { status: 'success', message: `Data source is working (${buckets} buckets)` };
|
||||
}
|
||||
console.log('InfluxDB Error', data);
|
||||
return { status: 'error', message: 'Error reading buckets' };
|
||||
})
|
||||
.catch((err: any) => {
|
||||
return { status: 'error', message: err.message };
|
||||
});
|
||||
}
|
||||
|
||||
const queryBuilder = new InfluxQueryBuilder({ measurement: '', tags: [] }, this.database);
|
||||
const query = queryBuilder.buildExploreQuery('RETENTION POLICIES');
|
||||
|
||||
|
@ -5,6 +5,9 @@ import InfluxStartPage from './components/InfluxStartPage';
|
||||
import { DataSourcePlugin } from '@grafana/data';
|
||||
import ConfigEditor from './components/ConfigEditor';
|
||||
|
||||
// This adds a directive that is used in the query editor
|
||||
import './components/FluxQueryEditor';
|
||||
|
||||
class InfluxAnnotationsQueryCtrl {
|
||||
static templateUrl = 'partials/annotations.editor.html';
|
||||
}
|
||||
|
@ -1,37 +1,10 @@
|
||||
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
|
||||
<div ng-if="ctrl.datasource.enableFlux" class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">QUERY</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select
|
||||
class="gf-form-input gf-size-auto"
|
||||
ng-model="ctrl.target.queryType"
|
||||
ng-options="f.value as f.text for f in ctrl.queryTypes"
|
||||
ng-change="ctrl.refresh()"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO use monaco flux editor -->
|
||||
<div ng-if="ctrl.target.queryType === 'Flux'">
|
||||
<div class="gf-form">
|
||||
<textarea
|
||||
rows="3"
|
||||
class="gf-form-input"
|
||||
ng-model="ctrl.target.query"
|
||||
spellcheck="false"
|
||||
placeholder="Flux Query"
|
||||
ng-model-onblur
|
||||
ng-change="ctrl.refresh()"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<query-editor-row ng-if="ctrl.datasource.is2x" query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="false">
|
||||
<flux-query-editor target="ctrl.target" change="ctrl.onChange" refresh="ctrl.refresh"></flux-query-editor>
|
||||
</query-editor-row>
|
||||
|
||||
<div ng-if="ctrl.target.queryType === 'InfluxQL' || !ctrl.target.queryType">
|
||||
<query-editor-row ng-if="!ctrl.datasource.is2x" query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
|
||||
<div ng-if="ctrl.target.rawQuery">
|
||||
<div class="gf-form">
|
||||
<textarea
|
||||
rows="3"
|
||||
@ -72,7 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.target.queryType === 'Classic'">
|
||||
<div ng-if="!ctrl.target.rawQuery">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">FROM</label>
|
||||
|
@ -5,16 +5,17 @@ import InfluxQueryModel from './influx_query_model';
|
||||
import queryPart from './query_part';
|
||||
import { QueryCtrl } from 'app/plugins/sdk';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { InfluxQueryType } from './types';
|
||||
import { InfluxQuery } from './types';
|
||||
import InfluxDatasource from './datasource';
|
||||
|
||||
export class InfluxQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'partials/query.editor.html';
|
||||
|
||||
datasource: InfluxDatasource;
|
||||
queryModel: InfluxQueryModel;
|
||||
queryBuilder: any;
|
||||
groupBySegment: any;
|
||||
resultFormats: any[];
|
||||
queryTypes: any[];
|
||||
orderByTime: any[];
|
||||
policySegment: any;
|
||||
tagSegments: any[];
|
||||
@ -39,15 +40,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
{ text: 'Table', value: 'table' },
|
||||
];
|
||||
|
||||
// Show a dropdown for flux
|
||||
if (this.datasource.enableFlux) {
|
||||
this.queryTypes = [
|
||||
{ text: 'Classic', value: InfluxQueryType.Classic },
|
||||
{ text: 'InfluxQL', value: InfluxQueryType.InfluxQL },
|
||||
{ text: 'Flux', value: InfluxQueryType.Flux },
|
||||
];
|
||||
}
|
||||
|
||||
this.policySegment = uiSegmentSrv.newSegment(this.target.policy);
|
||||
|
||||
if (!this.target.measurement) {
|
||||
@ -83,6 +75,10 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
onChange = (target: InfluxQuery) => {
|
||||
this.target.query = target.query;
|
||||
};
|
||||
|
||||
removeOrderByTime() {
|
||||
this.target.orderByTime = 'ASC';
|
||||
}
|
||||
@ -194,6 +190,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
return Promise.resolve([{ text: 'Remove', value: 'remove-part' }]);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
handleGroupByPartEvent(part: any, index: any, evt: { name: any }) {
|
||||
@ -218,6 +215,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
return Promise.resolve([{ text: 'Remove', value: 'remove-part' }]);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
fixTagSegments() {
|
||||
@ -247,21 +245,14 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
|
||||
// Only valid for InfluxQL queries
|
||||
toggleEditorMode() {
|
||||
try {
|
||||
this.target.query = this.queryModel.render(false);
|
||||
} catch (err) {
|
||||
console.log('query render error');
|
||||
}
|
||||
const { queryType } = this.target;
|
||||
if (queryType === InfluxQueryType.Flux || queryType === InfluxQueryType.InfluxQL) {
|
||||
this.target.queryType = InfluxQueryType.Classic;
|
||||
this.target.rawQuery = false;
|
||||
} else if (this.datasource.enableFlux) {
|
||||
this.target.queryType = InfluxQueryType.Flux;
|
||||
} else {
|
||||
this.target.queryType = InfluxQueryType.InfluxQL;
|
||||
}
|
||||
this.target.rawQuery = !this.target.rawQuery;
|
||||
}
|
||||
|
||||
getMeasurements(measurementFilter: any) {
|
||||
|
@ -1,14 +1,15 @@
|
||||
import '../query_ctrl';
|
||||
import { uiSegmentSrv } from 'app/core/services/segment_srv';
|
||||
import { InfluxQueryCtrl } from '../query_ctrl';
|
||||
import InfluxDatasource from '../datasource';
|
||||
|
||||
describe('InfluxDBQueryCtrl', () => {
|
||||
const ctx = {} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
InfluxQueryCtrl.prototype.datasource = {
|
||||
InfluxQueryCtrl.prototype.datasource = ({
|
||||
metricFindQuery: () => Promise.resolve([]),
|
||||
};
|
||||
} as unknown) as InfluxDatasource;
|
||||
InfluxQueryCtrl.prototype.target = { target: {} };
|
||||
InfluxQueryCtrl.prototype.panelCtrl = {
|
||||
panel: {
|
||||
|
@ -1,19 +1,28 @@
|
||||
import { DataQuery, DataSourceJsonData } from '@grafana/data';
|
||||
|
||||
export enum InfluxVersion {
|
||||
InfluxQL = 'InfluxQL',
|
||||
Flux = 'Flux',
|
||||
}
|
||||
|
||||
export interface InfluxOptions extends DataSourceJsonData {
|
||||
version?: InfluxVersion;
|
||||
|
||||
timeInterval: string;
|
||||
httpMode: string;
|
||||
|
||||
// Influx 2.0
|
||||
enableFlux?: boolean;
|
||||
// With Flux
|
||||
organization?: string;
|
||||
defaultBucket?: string;
|
||||
maxSeries?: number;
|
||||
}
|
||||
|
||||
export interface InfluxSecureJsonData {
|
||||
password?: string;
|
||||
// For Flux
|
||||
token?: string;
|
||||
|
||||
// In 1x a different password can be sent than then HTTP auth
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface InfluxQueryPart {
|
||||
@ -29,14 +38,7 @@ export interface InfluxQueryTag {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export enum InfluxQueryType {
|
||||
Classic = 'Classic', // IFQL query builder
|
||||
InfluxQL = 'InfluxQL', // raw ifql
|
||||
Flux = 'Flux',
|
||||
}
|
||||
|
||||
export interface InfluxQuery extends DataQuery {
|
||||
queryType?: InfluxQueryType;
|
||||
policy?: string;
|
||||
measurement?: string;
|
||||
resultFormat?: 'time_series' | 'table';
|
||||
@ -48,6 +50,6 @@ export interface InfluxQuery extends DataQuery {
|
||||
slimit?: string;
|
||||
tz?: string;
|
||||
fill?: string;
|
||||
rawQuery?: boolean; // deprecated (use raw InfluxQL)
|
||||
rawQuery?: boolean;
|
||||
query?: string;
|
||||
}
|
||||
|
@ -386,7 +386,7 @@ $panel-grid-placeholder-bg: darken(#1f60c4, 30%);
|
||||
$panel-grid-placeholder-shadow: 0 0 4px #3274d9;
|
||||
|
||||
// logs
|
||||
$logs-color-unkown: $gray-2;
|
||||
$logs-color-unknown: $gray-2;
|
||||
|
||||
// toggle-group
|
||||
$button-toggle-group-btn-active-bg: linear-gradient(90deg, #eb7b18, #d44a3a);
|
||||
|
@ -379,7 +379,7 @@ $panel-grid-placeholder-bg: lighten(#5794f2, 30%);
|
||||
$panel-grid-placeholder-shadow: 0 0 4px #5794f2;
|
||||
|
||||
// logs
|
||||
$logs-color-unkown: $gray-5;
|
||||
$logs-color-unknown: $gray-5;
|
||||
|
||||
// toggle-group
|
||||
$button-toggle-group-btn-active-bg: $brand-primary;
|
||||
|
Loading…
Reference in New Issue
Block a user