From 666656c925ae2fcc39d47e261a96f9466720ab29 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Tue, 6 Jul 2021 02:56:01 -0400 Subject: [PATCH] Loki: Add $__range variable (#36175) * Add variable to Loki * Add tests * Update, remove redundant options * Add missing import * Update docs/sources/datasources/loki.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/datasources/loki.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> --- docs/sources/datasources/loki.md | 3 ++ .../variable-types/global-variables.md | 2 +- .../datasource/loki/datasource.test.ts | 46 +++++++++++++++++-- .../app/plugins/datasource/loki/datasource.ts | 19 +++++++- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/docs/sources/datasources/loki.md b/docs/sources/datasources/loki.md index 332dcdb3d3e..e30071c7e2b 100644 --- a/docs/sources/datasources/loki.md +++ b/docs/sources/datasources/loki.md @@ -154,6 +154,9 @@ provides the following functions you can use in the `Query` input field. | `label_values(label)` | Returns a list of label values for the `label`. | | `label_values(log stream selector, label)` | Returns a list of label values for the `label` in the specified `log stream selector`.| +### Using interval and range variables + +You can use some global built-in variables in query variables; `$__interval`, `$__interval_ms`, `$__range`, `$__range_s` and `$__range_ms`. For more information, refer to [Global built-in variables]({{< relref "../variables/variable-types/global-variables.md" >}}). ## Annotations You can use any non-metric Loki query as a source for [annotations]({{< relref "../dashboards/annotations" >}}). Log content will be used as annotation text and your log stream labels as tags, so there is no need for additional mapping. diff --git a/docs/sources/variables/variable-types/global-variables.md b/docs/sources/variables/variable-types/global-variables.md index 1d66bb15ea8..224f5089263 100644 --- a/docs/sources/variables/variable-types/global-variables.md +++ b/docs/sources/variables/variable-types/global-variables.md @@ -70,7 +70,7 @@ This variable is the ID of the current organization. ## $__range -Currently only supported for Prometheus data sources. This variable represents the range for the current dashboard. It is calculated by `to - from`. It has a millisecond and a second representation called `$__range_ms` and `$__range_s`. +Currently only supported for Prometheus and Loki data sources. This variable represents the range for the current dashboard. It is calculated by `to - from`. It has a millisecond and a second representation called `$__range_ms` and `$__range_s`. ## $__rate_interval diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 5ad1792dc65..ffe64f18b50 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1,12 +1,13 @@ import { of, throwError } from 'rxjs'; import { take } from 'rxjs/operators'; -import { AnnotationQueryRequest, CoreApp, DataFrame, dateTime, FieldCache, TimeSeries } from '@grafana/data'; +import { AnnotationQueryRequest, CoreApp, DataFrame, dateTime, FieldCache, TimeSeries, toUtc } from '@grafana/data'; import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; import LokiDatasource from './datasource'; import { LokiQuery, LokiResponse, LokiResultType } from './types'; import { getQueryOptions } from 'test/helpers/getQueryOptions'; import { TemplateSrv } from 'app/features/templating/template_srv'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { backendSrv } from 'app/core/services/backend_srv'; import { CustomVariableModel } from '../../../features/variables/types'; import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer'; @@ -19,11 +20,21 @@ jest.mock('@grafana/runtime', () => ({ getBackendSrv: () => backendSrv, })); -const timeSrvStub = { +const rawRange = { + from: toUtc('2018-04-25 10:00'), + to: toUtc('2018-04-25 11:00'), +}; + +const timeSrvStub = ({ timeRange: () => ({ - from: new Date(0), - to: new Date(1), + from: rawRange.from, + to: rawRange.to, + raw: rawRange, }), +} as unknown) as TimeSrv; + +const templateSrvStub = { + replace: jest.fn((a: string, ...rest: any) => a), }; const testLogsResponse: FetchResponse = { @@ -322,6 +333,33 @@ describe('LokiDatasource', () => { ); }); }); + + describe('__range, __range_s and __range_ms variables', () => { + const options = { + targets: [{ expr: 'rate(process_cpu_seconds_total[$__range])', refId: 'A' }], + range: { + from: rawRange.from, + to: rawRange.to, + raw: rawRange, + }, + }; + + const ds = new LokiDatasource({} as any, templateSrvStub as any, timeSrvStub as any); + + beforeEach(() => { + templateSrvStub.replace.mockClear(); + }); + + it('should be correctly interpolated', () => { + ds.query(options as any); + const range = templateSrvStub.replace.mock.calls[0][1].__range; + const rangeMs = templateSrvStub.replace.mock.calls[0][1].__range_ms; + const rangeS = templateSrvStub.replace.mock.calls[0][1].__range_s; + expect(range).toEqual({ text: '3600s', value: '3600s' }); + expect(rangeMs).toEqual({ text: 3600000, value: 3600000 }); + expect(rangeS).toEqual({ text: 3600, value: 3600 }); + }); + }); }); describe('when interpolating variables', () => { diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 7a185884b37..379a73a17e4 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -23,6 +23,7 @@ import { LogRowModel, QueryResultMeta, ScopedVars, + TimeRange, } from '@grafana/data'; import { getTemplateSrv, TemplateSrv, BackendSrvRequest, FetchError, getBackendSrv } from '@grafana/runtime'; import { addLabelToQuery } from 'app/plugins/datasource/prometheus/add_label_to_query'; @@ -95,11 +96,15 @@ export class LokiDatasource extends DataSourceApi { query(options: DataQueryRequest): Observable { const subQueries: Array> = []; + const scopedVars = { + ...options.scopedVars, + ...this.getRangeScopedVars(options.range), + }; const filteredTargets = options.targets .filter((target) => target.expr && !target.hide) .map((target) => ({ ...target, - expr: this.templateSrv.replace(target.expr, options.scopedVars, this.interpolateQueryExpr), + expr: this.templateSrv.replace(target.expr, scopedVars, this.interpolateQueryExpr), })); for (const target of filteredTargets) { @@ -219,6 +224,7 @@ export class LokiDatasource extends DataSourceApi { return this.runLiveQuery(target, maxDataPoints); } const query = this.createRangeQuery(target, options, maxDataPoints); + return this._request(RANGE_QUERY_ENDPOINT, query).pipe( catchError((err: any) => this.throwUnless(err, err.status === 404, target)), switchMap((response: { data: LokiResponse; status: number }) => @@ -270,6 +276,16 @@ export class LokiDatasource extends DataSourceApi { ); }; + getRangeScopedVars(range: TimeRange = this.timeSrv.timeRange()) { + const msRange = range.to.diff(range.from); + const sRange = Math.round(msRange / 1000); + return { + __range_ms: { text: msRange, value: msRange }, + __range_s: { text: sRange, value: sRange }, + __range: { text: sRange + 's', value: sRange + 's' }, + }; + } + interpolateVariablesInQueries(queries: LokiQuery[], scopedVars: ScopedVars): LokiQuery[] { let expandedQueries = queries; if (queries && queries.length) { @@ -305,6 +321,7 @@ export class LokiDatasource extends DataSourceApi { if (!query) { return Promise.resolve([]); } + const interpolated = this.templateSrv.replace(query, {}, this.interpolateQueryExpr); return await this.processMetricFindQuery(interpolated); }