+
-
+ {ExtraFieldElement}
>
);
}
diff --git a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreExtraField.test.tsx.snap b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreExtraField.test.tsx.snap
deleted file mode 100644
index 0a968e90b02..00000000000
--- a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreExtraField.test.tsx.snap
+++ /dev/null
@@ -1,26 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`LokiExploreExtraField should render component 1`] = `
-
-
-
- Loki Explore Extra Field
-
-
-
-
-`;
diff --git a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap
index 0a46681c9b3..fc69ac05249 100644
--- a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap
+++ b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap
@@ -4,12 +4,11 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
}
data={
diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts
index 80abc8d10c6..4696f839b8f 100644
--- a/public/app/plugins/datasource/loki/datasource.test.ts
+++ b/public/app/plugins/datasource/loki/datasource.test.ts
@@ -1,7 +1,6 @@
import { of, throwError } from 'rxjs';
import { take } from 'rxjs/operators';
-import { omit } from 'lodash';
-import { AnnotationQueryRequest, CoreApp, DataFrame, dateTime, FieldCache, TimeRange } from '@grafana/data';
+import { AnnotationQueryRequest, CoreApp, DataFrame, dateTime, FieldCache, TimeRange, TimeSeries } from '@grafana/data';
import { BackendSrvRequest, FetchResponse } from '@grafana/runtime';
import LokiDatasource from './datasource';
@@ -26,7 +25,7 @@ const timeSrvStub = {
}),
};
-const testResponse: FetchResponse
= {
+const testLogsResponse: FetchResponse = {
data: {
data: {
resultType: LokiResultType.Stream,
@@ -49,6 +48,29 @@ const testResponse: FetchResponse = {
config: ({} as unknown) as BackendSrvRequest,
};
+const testMetricsResponse: FetchResponse = {
+ data: {
+ data: {
+ resultType: LokiResultType.Matrix,
+ result: [
+ {
+ metric: {},
+ values: [[1605715380, '1.1']],
+ },
+ ],
+ },
+ status: 'success',
+ },
+ ok: true,
+ headers: ({} as unknown) as Headers,
+ redirected: false,
+ status: 200,
+ statusText: 'OK',
+ type: 'basic',
+ url: '',
+ config: ({} as unknown) as BackendSrvRequest,
+};
+
describe('LokiDatasource', () => {
const fetchMock = jest.spyOn(backendSrv, 'fetch');
@@ -96,7 +118,7 @@ describe('LokiDatasource', () => {
});
});
- describe('when querying with limits', () => {
+ describe('when doing logs queries with limits', () => {
const runLimitTest = async ({
maxDataPoints = 123,
queryMaxLines,
@@ -121,7 +143,7 @@ describe('LokiDatasource', () => {
const options = getQueryOptions({ targets: [{ expr, refId: 'B', maxLines: queryMaxLines }] });
options.maxDataPoints = maxDataPoints;
- fetchMock.mockImplementation(() => of(testResponse));
+ fetchMock.mockImplementation(() => of(testLogsResponse));
await expect(ds.query(options).pipe(take(1))).toEmitValuesWith(() => {
expect(fetchMock.mock.calls.length).toBe(1);
@@ -151,10 +173,10 @@ describe('LokiDatasource', () => {
});
describe('when querying', () => {
- function setup(expr: string, app: CoreApp) {
+ function setup(expr: string, app: CoreApp, instant?: boolean, range?: boolean) {
const ds = createLokiDSForTests();
const options = getQueryOptions({
- targets: [{ expr, refId: 'B' }],
+ targets: [{ expr, refId: 'B', instant, range }],
app,
});
ds.runInstantQuery = jest.fn(() => of({ data: [] }));
@@ -162,68 +184,95 @@ describe('LokiDatasource', () => {
return { ds, options };
}
- it('should run range and instant query in Explore if running metric query', async () => {
- const { ds, options } = setup('rate({job="grafana"}[10m])', CoreApp.Explore);
+ const metricsQuery = 'rate({job="grafana"}[10m])';
+ const logsQuery = '{job="grafana"} |= "foo"';
+
+ it('should run logs instant if only instant is selected', async () => {
+ const { ds, options } = setup(logsQuery, CoreApp.Explore, true, false);
await ds.query(options).toPromise();
expect(ds.runInstantQuery).toBeCalled();
- expect(ds.runRangeQuery).toBeCalled();
+ expect(ds.runRangeQuery).not.toBeCalled();
});
- it('should run only range query in Explore if running logs query', async () => {
- const { ds, options } = setup('{job="grafana"}', CoreApp.Explore);
+ it('should run metrics instant if only instant is selected', async () => {
+ const { ds, options } = setup(metricsQuery, CoreApp.Explore, true, false);
+ await ds.query(options).toPromise();
+ expect(ds.runInstantQuery).toBeCalled();
+ expect(ds.runRangeQuery).not.toBeCalled();
+ });
+
+ it('should run only logs range query if only range is selected', async () => {
+ const { ds, options } = setup(logsQuery, CoreApp.Explore, false, true);
await ds.query(options).toPromise();
expect(ds.runInstantQuery).not.toBeCalled();
expect(ds.runRangeQuery).toBeCalled();
});
- it('should run only range query in Dashboard', async () => {
- const { ds, options } = setup('rate({job="grafana"}[10m])', CoreApp.Dashboard);
+ it('should run only metrics range query if only range is selected', async () => {
+ const { ds, options } = setup(metricsQuery, CoreApp.Explore, false, true);
await ds.query(options).toPromise();
expect(ds.runInstantQuery).not.toBeCalled();
expect(ds.runRangeQuery).toBeCalled();
});
- it('should return series data for both queries in Explore if metrics query', async () => {
+ it('should run only logs range query if no query type is selected in Explore', async () => {
+ const { ds, options } = setup(logsQuery, CoreApp.Explore);
+ await ds.query(options).toPromise();
+ expect(ds.runInstantQuery).not.toBeCalled();
+ expect(ds.runRangeQuery).toBeCalled();
+ });
+
+ it('should run only metrics range query if no query type is selected in Explore', async () => {
+ const { ds, options } = setup(metricsQuery, CoreApp.Explore);
+ await ds.query(options).toPromise();
+ expect(ds.runInstantQuery).not.toBeCalled();
+ expect(ds.runRangeQuery).toBeCalled();
+ });
+
+ it('should run only logs range query in Dashboard', async () => {
+ const { ds, options } = setup(logsQuery, CoreApp.Dashboard);
+ await ds.query(options).toPromise();
+ expect(ds.runInstantQuery).not.toBeCalled();
+ expect(ds.runRangeQuery).toBeCalled();
+ });
+
+ it('should run only metrics range query in Dashboard', async () => {
+ const { ds, options } = setup(metricsQuery, CoreApp.Dashboard);
+ await ds.query(options).toPromise();
+ expect(ds.runInstantQuery).not.toBeCalled();
+ expect(ds.runRangeQuery).toBeCalled();
+ });
+
+ it('should return series data for metrics range queries', async () => {
const ds = createLokiDSForTests();
const options = getQueryOptions({
- targets: [{ expr: 'rate({job="grafana"} |= "foo" [10m])', refId: 'B' }],
+ targets: [{ expr: metricsQuery, refId: 'B', range: true }],
app: CoreApp.Explore,
});
- fetchMock
- .mockImplementationOnce(() => of(testResponse))
- .mockImplementation(() => of(omit(testResponse, 'data.status')));
+ fetchMock.mockImplementation(() => of(testMetricsResponse));
await expect(ds.query(options)).toEmitValuesWith(received => {
- // first result always comes from runInstantQuery
- const firstResult = received[0];
- expect(firstResult).toEqual({ data: [], key: 'B_instant' });
+ const result = received[0];
+ const timeSeries = result.data[0] as TimeSeries;
- // second result always comes from runRangeQuery
- const secondResult = received[1];
- const dataFrame = secondResult.data[0] as DataFrame;
- const fieldCache = new FieldCache(dataFrame);
-
- expect(fieldCache.getFieldByName('line')?.values.get(0)).toBe('hello');
- expect(dataFrame.meta?.limit).toBe(500);
- expect(dataFrame.meta?.searchWords).toEqual([]);
+ expect(timeSeries.meta?.preferredVisualisationType).toBe('graph');
+ expect(timeSeries.refId).toBe('B');
+ expect(timeSeries.datapoints[0]).toEqual([1.1, 1605715380000]);
});
});
- it('should return series data for range query in Dashboard', async () => {
+ it('should return series data for logs range query', async () => {
const ds = createLokiDSForTests();
const options = getQueryOptions({
- targets: [{ expr: '{job="grafana"} |= "foo"', refId: 'B' }],
+ targets: [{ expr: logsQuery, refId: 'B' }],
});
- fetchMock
- .mockImplementationOnce(() => of(testResponse))
- .mockImplementation(() => of(omit(testResponse, 'data.status')));
+ fetchMock.mockImplementation(() => of(testLogsResponse));
await expect(ds.query(options)).toEmitValuesWith(received => {
- // first result will come from runRangeQuery
- const firstResult = received[0];
- const dataFrame = firstResult.data[0] as DataFrame;
+ const result = received[0];
+ const dataFrame = result.data[0] as DataFrame;
const fieldCache = new FieldCache(dataFrame);
expect(fieldCache.getFieldByName('line')?.values.get(0)).toBe('hello');
diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts
index 93ba8db9131..9ba4d515c92 100644
--- a/public/app/plugins/datasource/loki/datasource.ts
+++ b/public/app/plugins/datasource/loki/datasource.ts
@@ -24,13 +24,17 @@ import {
QueryResultMeta,
ScopedVars,
TimeRange,
- CoreApp,
} from '@grafana/data';
import { getTemplateSrv, TemplateSrv, BackendSrvRequest, FetchError, getBackendSrv } from '@grafana/runtime';
import { addLabelToQuery } from 'app/plugins/datasource/prometheus/add_label_to_query';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { convertToWebSocketUrl } from 'app/core/utils/explore';
-import { lokiResultsToTableModel, lokiStreamResultToDataFrame, processRangeQueryResponse } from './result_transformer';
+import {
+ lokiResultsToTableModel,
+ lokiStreamResultToDataFrame,
+ lokiStreamsToDataFrames,
+ processRangeQueryResponse,
+} from './result_transformer';
import { getHighlighterExpressionsFromQuery } from './query_utils';
import {
@@ -99,12 +103,11 @@ export class LokiDatasource extends DataSourceApi {
}));
for (const target of filteredTargets) {
- // In explore we want to show result of metrics instant query in a table under the graph panel to mimic behaviour of prometheus.
- // We don't want to do that in dashboards though as user would have to pick the correct data frame.
- if (options.app === CoreApp.Explore && isMetricsQuery(target.expr)) {
+ if (target.instant) {
subQueries.push(this.runInstantQuery(target, options, filteredTargets.length));
+ } else {
+ subQueries.push(this.runRangeQuery(target, options, filteredTargets.length));
}
- subQueries.push(this.runRangeQuery(target, options, filteredTargets.length));
}
// No valid targets, return the empty result to save a round trip.
@@ -124,12 +127,14 @@ export class LokiDatasource extends DataSourceApi {
responseListLength: number
): Observable => {
const timeNs = this.getTime(options.range.to, true);
+ const queryLimit = isMetricsQuery(target.expr) ? options.maxDataPoints : target.maxLines;
const query = {
query: target.expr,
time: `${timeNs + (1e9 - (timeNs % 1e9))}`,
- limit: Math.min(options.maxDataPoints || Infinity, this.maxLines),
+ limit: Math.min(queryLimit || Infinity, this.maxLines),
};
- /** Show results of Loki instant queries only in table */
+
+ /** Used only for results of metrics instant queries */
const meta: QueryResultMeta = {
preferredVisualisationType: 'table',
};
@@ -138,7 +143,14 @@ export class LokiDatasource extends DataSourceApi {
map((response: { data: LokiResponse }) => {
if (response.data.data.resultType === LokiResultType.Stream) {
return {
- data: [],
+ data: response.data
+ ? lokiStreamsToDataFrames(
+ response.data as LokiStreamResponse,
+ target,
+ query.limit,
+ this.instanceSettings.jsonData
+ )
+ : [],
key: `${target.refId}_instant`,
};
}
diff --git a/public/app/plugins/datasource/loki/result_transformer.test.ts b/public/app/plugins/datasource/loki/result_transformer.test.ts
index 0f1cc7a79e3..73d20dbfd84 100644
--- a/public/app/plugins/datasource/loki/result_transformer.test.ts
+++ b/public/app/plugins/datasource/loki/result_transformer.test.ts
@@ -61,10 +61,10 @@ describe('loki result transformer', () => {
});
});
- describe('lokiStreamsToDataframes', () => {
+ describe('lokiStreamsToDataFrames', () => {
it('should enhance data frames', () => {
jest.spyOn(ResultTransformer, 'enhanceDataFrame');
- const dataFrames = ResultTransformer.lokiStreamsToDataframes(lokiResponse, { refId: 'B' }, 500, {
+ const dataFrames = ResultTransformer.lokiStreamsToDataFrames(lokiResponse, { refId: 'B' }, 500, {
derivedFields: [
{
matcherRegex: 'trace=(w+)',
diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts
index 092eb7ebd2f..3788224d568 100644
--- a/public/app/plugins/datasource/loki/result_transformer.ts
+++ b/public/app/plugins/datasource/loki/result_transformer.ts
@@ -305,7 +305,7 @@ function lokiStatsToMetaStat(stats: LokiStats | undefined): QueryResultMetaStat[
return result;
}
-export function lokiStreamsToDataframes(
+export function lokiStreamsToDataFrames(
response: LokiStreamResponse,
target: { refId: string; expr?: string },
limit: number,
@@ -472,7 +472,7 @@ export function processRangeQueryResponse(
switch (response.data.resultType) {
case LokiResultType.Stream:
return of({
- data: lokiStreamsToDataframes(response as LokiStreamResponse, target, limit, config, reverse),
+ data: lokiStreamsToDataFrames(response as LokiStreamResponse, target, limit, config, reverse),
key: `${target.refId}_log`,
});
diff --git a/public/app/plugins/datasource/loki/types.ts b/public/app/plugins/datasource/loki/types.ts
index cb09ff3dd5c..0b751fb73f5 100644
--- a/public/app/plugins/datasource/loki/types.ts
+++ b/public/app/plugins/datasource/loki/types.ts
@@ -30,6 +30,8 @@ export interface LokiQuery extends DataQuery {
legendFormat?: string;
valueWithRefId?: boolean;
maxLines?: number;
+ range?: boolean;
+ instant?: boolean;
}
export interface LokiOptions extends DataSourceJsonData {
diff --git a/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx b/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx
index 2c05ed14c31..80bcf7c971f 100644
--- a/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx
+++ b/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx
@@ -23,7 +23,7 @@ export const PromExploreExtraField: React.FC = memo(
return (
- {/*QueryTypeField */}
+ {/*Query type field*/}