diff --git a/README.md b/README.md index d8b776a..5e977e5 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Read more about InfluxDB here: Getting started: -1. Install a recent [InfluxDB nightly](https://portal.influxdata.com/downloads), then run `influxd -config` and catch that config as config.toml. Then run `influxd -config config.toml`. The recent nighlies contain the Flux engine. +1. Install [InfluxDB 1.7+](https://portal.influxdata.com/downloads), then edit `influxdb.conf` setting [`[http] flux-enabled = true`](https://docs.influxdata.com/influxdb/v1.7/administration/config#flux-enabled-false) See also: [https://docs.influxdata.com/flux/v0.7/introduction/installation/](https://docs.influxdata.com/flux/v0.7/introduction/installation/) 2. Install telegraph to get some data: brew install telegraf. Then run telegraf. diff --git a/specs/datasource.jest.ts b/specs/datasource.jest.ts index df6895b..0399a2c 100644 --- a/specs/datasource.jest.ts +++ b/specs/datasource.jest.ts @@ -6,9 +6,9 @@ import Datasource from '../src/datasource'; describe('InfluxDB (Flux)', () => { const templateSrv = new TemplateSrv(); - const ds = new Datasource({ url: '' }, {}, templateSrv); + const ds = new Datasource({url: ''}, {}, templateSrv); const DEFAULT_OPTIONS = { - rangeRaw: { to: 'now', from: 'now - 3h' }, + rangeRaw: {to: 'now', from: 'now - 3h'}, scopedVars: {}, targets: [], }; @@ -18,23 +18,28 @@ describe('InfluxDB (Flux)', () => { let target: any; it.skip('replaces $range variable', () => { - target = ds.prepareQueryTarget({ query: 'from(db: "test") |> range($range)' }, DEFAULT_OPTIONS); - expect(target.query).toBe('from(db: "test") |> range(start: -3h)'); + target = ds.prepareQueryTarget( + {query: 'from(bucket: "test") |> range($range)'}, + DEFAULT_OPTIONS + ); + expect(target.query).toBe('from(bucket: "test") |> range(start: -3h)'); }); it.skip('replaces $range variable with custom dates', () => { const to = moment(); const from = moment().subtract(1, 'hours'); target = ds.prepareQueryTarget( - { query: 'from(db: "test") |> range($range)' }, + {query: 'from(bucket: "test") |> range($range)'}, { ...DEFAULT_OPTIONS, - rangeRaw: { to, from }, + rangeRaw: {to, from}, } ); const start = from.toISOString(); const stop = to.toISOString(); - expect(target.query).toBe(`from(db: "test") |> range(start: ${start}, stop: ${stop})`); + expect(target.query).toBe( + `from(bucket: "test") |> range(start: ${start}, stop: ${stop})` + ); }); }); }); diff --git a/specs/metric_find_query.jest.ts b/specs/metric_find_query.jest.ts index ad1c102..d2678c6 100644 --- a/specs/metric_find_query.jest.ts +++ b/specs/metric_find_query.jest.ts @@ -3,7 +3,7 @@ import expandMacros from '../src/metric_find_query'; describe('metric find query', () => { describe('expandMacros()', () => { it('returns a non-macro query unadulterated', () => { - const query = 'from(db:"telegraf") |> last()'; + const query = 'from(bucket:"telegraf") |> last()'; const result = expandMacros(query); expect(result).toBe(query); }); @@ -12,22 +12,24 @@ describe('metric find query', () => { const query = ' measurements(mydb) '; const result = expandMacros(query).replace(/\s/g, ''); expect(result).toBe( - 'from(db:"mydb")|>range($range)|>group(by:["_measurement"])|>distinct(column:"_measurement")|>group(none:true)' + 'from(bucket:"mydb")|>range($range)|>group(by:["_measurement"])|>distinct(column:"_measurement")|>group(none:true)' ); }); it('returns a tags query for tags()', () => { const query = ' tags(mydb , mymetric) '; const result = expandMacros(query).replace(/\s/g, ''); - expect(result).toBe('from(db:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")|>keys()'); + expect(result).toBe( + 'from(bucket:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")|>keys()' + ); }); it('returns a tag values query for tag_values()', () => { const query = ' tag_values(mydb , mymetric, mytag) '; const result = expandMacros(query).replace(/\s/g, ''); expect(result).toBe( - 'from(db:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")' + - '|>group(by:["mytag"])|>distinct(column:"mytag")|>group(none:true)' + 'from(bucket:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")' + + '|>group(by:["mytag"])|>distinct(column:"mytag")|>group(none:true)' ); }); @@ -35,8 +37,8 @@ describe('metric find query', () => { const query = ' field_keys(mydb , mymetric) '; const result = expandMacros(query).replace(/\s/g, ''); expect(result).toBe( - 'from(db:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")' + - '|>group(by:["_field"])|>distinct(column:"_field")|>group(none:true)' + 'from(bucket:"mydb")|>range($range)|>filter(fn:(r)=>r._measurement=="mymetric")' + + '|>group(by:["_field"])|>distinct(column:"_field")|>group(none:true)' ); }); }); diff --git a/src/datasource.ts b/src/datasource.ts index aa58570..eb13e53 100644 --- a/src/datasource.ts +++ b/src/datasource.ts @@ -18,8 +18,7 @@ export default class InfluxDatasource { username: string; password: string; name: string; - orgName: string; - database: any; + bucket: any; basicAuth: any; withCredentials: any; interval: any; @@ -34,11 +33,10 @@ export default class InfluxDatasource { this.username = instanceSettings.username; this.password = instanceSettings.password; this.name = instanceSettings.name; - this.orgName = instanceSettings.orgName || 'defaultorgname'; this.basicAuth = instanceSettings.basicAuth; this.withCredentials = instanceSettings.withCredentials; this.interval = (instanceSettings.jsonData || {}).timeInterval; - this.database = (instanceSettings.jsonData || {}).database; + this.bucket = (instanceSettings.jsonData || {}).bucket; this.supportAnnotations = true; this.supportMetrics = true; } @@ -133,13 +131,21 @@ export default class InfluxDatasource { if (!query) { return Promise.resolve({data: ''}); } - return this._influxRequest('POST', '/api/v2/query', {query: query}, options); + return this._influxRequest('POST', '/api/v2/query', query, options); } testDatasource() { - const query = `from(bucket:"${this.database}") |> last()`; + const query = `from(bucket:"${this.bucket}") + |> range(start:-10y) + |> last()`; + if (this.bucket.indexOf('/') < 0) { + return Promise.resolve({ + status: 'error', + message: 'The bucket is missing a retention policy', + }); + } - return this._influxRequest('POST', '/api/v2/query', {query: query}) + return this._influxRequest('POST', '/api/v2/query', query) .then(res => { if (res && res.data && res.data.trim()) { return { @@ -150,7 +156,7 @@ export default class InfluxDatasource { return { status: 'error', message: - 'Data source connected, but has no data. Verify the "Database" field and make sure the database has data.', + 'Data source connected, but has no data. Verify the "bucket" field and make sure the bucket has data.', }; }) .catch(err => { @@ -158,10 +164,8 @@ export default class InfluxDatasource { }); } - _influxRequest(method: string, url: string, data: any, options?: any) { - let params: any = { - organization: `my-org`, - }; + _influxRequest(method: string, url: string, query: string, options?: any) { + let params: any = {}; if (this.username) { params.u = this.username; @@ -172,12 +176,15 @@ export default class InfluxDatasource { method: method, url: this.url + url, params: params, - data: data, + data: query, precision: 'ms', inspect: {type: this.type}, }; - req.headers = {}; + req.headers = { + Accept: 'application/csv', + 'Content-Type': 'application/vnd.flux', + }; if (this.basicAuth || this.withCredentials) { req.withCredentials = true; diff --git a/src/editor/FluxQueryField.tsx b/src/editor/FluxQueryField.tsx index 291c8d5..931e24b 100644 --- a/src/editor/FluxQueryField.tsx +++ b/src/editor/FluxQueryField.tsx @@ -26,14 +26,14 @@ const wrapText = text => ({ text }); const RATE_RANGES = ['1m', '5m', '10m', '30m', '1h']; const DEFAULT_DATABASE = 'telegraf'; -function expandQuery(database, measurement, field) { +function expandQuery(bucket, measurement, field) { if (field) { return ( - `from(db: "${database}")\n` + + `from(bucket: "${bucket}")\n` + ` |> filter(fn: (r) => r["_measurement"] == "${measurement}" AND r["_field"] == "${field}")\n |> range($range)\n |> limit(n: 1000)` ); } - return `from(db: "${database}")\n |> filter(fn: (r) => r["_measurement"] == "${measurement}")\n |> range($range)\n |> limit(n: 1000)`; + return `from(bucket: "${bucket}")\n |> filter(fn: (r) => r["_measurement"] == "${measurement}")\n |> range($range)\n |> limit(n: 1000)`; } export default class FluxQueryField extends QueryField { @@ -154,7 +154,7 @@ export default class FluxQueryField extends QueryField { suggestionGroups.push({ prefixMatch: true, label: 'Templates', - items: [`from(db: "${database}") |> range($range) `].map(wrapText), + items: [`from(bucket: "${database}") |> range($range) `].map(wrapText), }); suggestionGroups.push({ prefixMatch: true, diff --git a/src/editor/flux.ts b/src/editor/flux.ts index 81f0c12..bd19049 100644 --- a/src/editor/flux.ts +++ b/src/editor/flux.ts @@ -37,8 +37,8 @@ export const FUNCTIONS = [ }, { text: 'from', - display: 'from(db: "database)', - hint: 'Starting point of a query, produces a table from the given "db".', + display: 'from(bucket: "database/policy")', + hint: 'Starting point of a query, produces a table from the given bucket.', }, { text: 'group', diff --git a/src/metric_find_query.ts b/src/metric_find_query.ts index 4356fbd..6382337 100644 --- a/src/metric_find_query.ts +++ b/src/metric_find_query.ts @@ -16,7 +16,7 @@ export default function expandMacros(query) { const measurementsQuery = query.match(MEASUREMENTS_REGEXP); if (measurementsQuery) { const database = measurementsQuery[1]; - return `from(db:"${database}") + return `from(bucket:"${database}") |> range($range) |> group(by:["_measurement"]) |> distinct(column:"_measurement") @@ -27,7 +27,7 @@ export default function expandMacros(query) { if (tagsQuery) { const database = tagsQuery[1]; const measurement = tagsQuery[2]; - return `from(db:"${database}") + return `from(bucket:"${database}") |> range($range) |> filter(fn:(r) => r._measurement == "${measurement}") |> keys()`; @@ -38,7 +38,7 @@ export default function expandMacros(query) { const database = tagValuesQuery[1]; const measurement = tagValuesQuery[2]; const tag = tagValuesQuery[3]; - return `from(db:"${database}") + return `from(bucket:"${database}") |> range($range) |> filter(fn:(r) => r._measurement == "${measurement}") |> group(by:["${tag}"]) @@ -50,7 +50,7 @@ export default function expandMacros(query) { if (fieldKeysQuery) { const database = fieldKeysQuery[1]; const measurement = fieldKeysQuery[2]; - return `from(db:"${database}") + return `from(bucket:"${database}") |> range($range) |> filter(fn:(r) => r._measurement == "${measurement}") |> group(by:["_field"]) diff --git a/src/partials/annotations.editor.html b/src/partials/annotations.editor.html index 53407f2..ed8459e 100644 --- a/src/partials/annotations.editor.html +++ b/src/partials/annotations.editor.html @@ -1,6 +1,6 @@
- +
diff --git a/src/partials/config.html b/src/partials/config.html index 07436a1..2693cbd 100644 --- a/src/partials/config.html +++ b/src/partials/config.html @@ -6,14 +6,17 @@

InfluxDB Details

- Default Database - + Default Bucket + + +

A combination of the default database and retention policy

+
- User + Username
diff --git a/src/query_ctrl.ts b/src/query_ctrl.ts index b0bf33c..53b900b 100644 --- a/src/query_ctrl.ts +++ b/src/query_ctrl.ts @@ -1,10 +1,10 @@ import appEvents from 'grafana/app/core/app_events'; -import { QueryCtrl } from 'grafana/app/plugins/sdk'; +import {QueryCtrl} from 'grafana/app/plugins/sdk'; import './editor/editor_component'; -function makeDefaultQuery(database) { - return `from(db: "${database}") +function makeDefaultQuery(bucket) { + return `from(bucket: "${bucket}") |> range($range) |> limit(n:1000) `; @@ -13,7 +13,7 @@ export class InfluxFluxQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; dataPreview: string; - defaultDatabase: string; + defaultBucket: string; resultRecordCount: string; resultTableCount: string; resultFormats: any[]; @@ -26,11 +26,14 @@ export class InfluxFluxQueryCtrl extends QueryCtrl { this.resultTableCount = ''; if (this.target.query === undefined) { - this.target.query = makeDefaultQuery(this.datasource.database); + this.target.query = makeDefaultQuery(this.datasource.bucket); } - this.defaultDatabase = this.datasource.database; - this.resultFormats = [{ text: 'Time series', value: 'time_series' }, { text: 'Table', value: 'table' }]; + this.defaultBucket = this.datasource.bucket; + this.resultFormats = [ + {text: 'Time series', value: 'time_series'}, + {text: 'Table', value: 'table'}, + ]; appEvents.on('ds-request-response', this.onResponseReceived, $scope); this.panelCtrl.events.on('refresh', this.onRefresh, $scope); @@ -39,7 +42,8 @@ export class InfluxFluxQueryCtrl extends QueryCtrl { onDataReceived = dataList => { this.resultRecordCount = dataList.reduce((count, model) => { - const records = model.type === 'table' ? model.rows.length : model.datapoints.length; + const records = + model.type === 'table' ? model.rows.length : model.datapoints.length; return count + records; }, 0); this.resultTableCount = dataList.length;