Explore: Adds Loki explore query editor (#21497)

* Explore: updates grafana-data explore query field props with explore mode

* Explore: updates query row to pass down explore mode to query fields

* Explore: adds LokiExploreQueryEditor

* Explore: updates loki query field form to render children

* Explore: adds loki explore extra field component

* Explore: adds extra field element to loki query field form

* Explore: updates loki explore query editor to use extra field element

* Explore: moves ExploreMode to grafana-data

* Explore: updates query row limit string

* Explore: adds maxLines to DataQuery

* Explore: adds maxLines to loki datasource runRangeQueryWithFallback

* Explore: adds onChangeQueryLimit to LokiExploreQueryEditor

* Explore: updates loki explore query editor to render extra field only in logs mode

* Explore: fixes query limits for live and legacy queries

* Explore: fixes result processor max lines limit in get logs result

* Explore: fixes Loki datasource limit test

* Explore: removes unnecessary ExploreMode from Loki language provider

* Explore: fixes formatting

* Explore: updates grafana-data datasource types - replaces strings with explore mode enum

* Explore: updates loki explore query field props to take ReactNode

* Explore: updates the way we calculate loki query lines limit to fall back to 0 lines on negative or invalid input instead of datasource maxLines

* Explore: updates result processor get logs result method to avoid counting invalid/negative line limits

* Explore: updates loki result transformer to process only an appropriate slice of a result instead of an entire one

* Explore: adds a method for query limit preprocessing/mapping

* Explore: updates loki datasource run range query with fallback method to use options.maxDataPoints in dashboards

* Explore: removes unnecessary maxlineslimt from getLogsResult in resultProcessor

* Explore: moves line limit to metadata

* Explore: adds an ability to specify input type of extra field

* Explore: updates LokiExploreQueryEditor - adds an input type

* Explore: updates LokiExploreQueryEditor to run queries when maxLines is positive

* Explore: fixes failing import of ExploreMode

* Explore: fixes reducers test imports formatting

* Explore: updates Loki extra field with min value set to 0

* Explore: exports LokiExploreExtraFieldProps

* Explore: adds render test of LokiExploreQueryEditor

* Explore: adds LokiExploreQueryEditor snapshot

* Explore: updates LokiExploreQueryEditor onChangeQueryLimit method to prevent it from running when query input is empty - fixes cheatsheet display issue

* Explore: updates Loki editor snapshots

* Explore: fixes typo in test set name in LokiExploreQueryEditor

* Explore: adds a render test of LokiExploreExtraField

* Explore: fixes typo in LokiExploreQueryEditor

* Explore: updates LokiExploreQueryEditor snapshot due to timezone issues

* Explore: updates LokiExploreExtraField to export both functional component and a version using memo

* Explore: updates LokiExploreQueryEditor to export both functional component and memoized function

* Explore: updates LokiExploreQueryEditor - removes unnecessary react fragment

* Explore: updates LokiExploreQueryEditor snapshot

* Explore: adds LokiExploreQueryEditor tests for different explore mode cases

* Explore: fixes Loki datasource and result transformer

* Explore: updates LokiExploreQueryEditor snapshot

* Explore: updates LokiExploreQueryEditor tests and test setup

* Explore: updates LokiExploreQueryEditor - refactors component

* Explore: updates LokiExploreQueryEditor to use default import from LokiExploreExtraField

* Explore: updates LokiExploreQueryEditor snapshot

* Explore: fixes formatting

* Explore: updates LokiExploreQueryEditor max lines change

* Explore: updates LokiExploreQueryEditor tests checking ExtraFieldElement

* Explore: adds mock loki datasource to LokiExploreQueryEditor

* Explore: updates LokiExploreQueryEditor test mock - adds language provider

* Explore: updates LokiExploreQueryEditor snapshot

* Explore: updates Loki ResultTransformer to filter out rows on limit - logic to be moved into a component with new form styles

* Explore: updates LokiExploreQueryEditor tests
This commit is contained in:
Lukas Siatka 2020-02-06 12:34:52 +00:00 committed by GitHub
parent 9b9f1ad1b9
commit df48d1c19f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 467 additions and 51 deletions

View File

@ -312,6 +312,11 @@ export enum DataSourceStatus {
Disconnected,
}
export enum ExploreMode {
Logs = 'Logs',
Metrics = 'Metrics',
}
export interface ExploreQueryFieldProps<
DSType extends DataSourceApi<TQuery, TOptions>,
TQuery extends DataQuery = DataQuery,
@ -320,11 +325,12 @@ export interface ExploreQueryFieldProps<
history: any[];
onBlur?: () => void;
absoluteRange?: AbsoluteTimeRange;
exploreMode?: ExploreMode;
}
export interface ExploreStartPageProps {
datasource?: DataSourceApi;
exploreMode: 'Logs' | 'Metrics';
exploreMode: ExploreMode;
onClickExample: (query: DataQuery) => void;
}
@ -384,6 +390,11 @@ export interface DataQuery {
datasource?: string | null;
metric?: any;
/**
* For limiting result lines.
*/
maxLines?: number;
}
export interface DataQueryError {
@ -414,7 +425,7 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
app: CoreApp | string;
cacheTimeout?: string;
exploreMode?: 'Logs' | 'Metrics';
exploreMode?: ExploreMode;
rangeRaw?: RawTimeRange;
timeInfo?: string; // The query time description (blue text in the upper right)

View File

@ -353,11 +353,17 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi
}
const limits = logSeries.filter(series => series.meta && series.meta.limit);
const limitValue = Object.values(
limits.reduce((acc: any, elem: any) => {
acc[elem.refId] = elem.meta.limit;
return acc;
}, {})
).reduce((acc: number, elem: any) => (acc += elem), 0);
if (limits.length > 0) {
meta.push({
label: 'Limit',
value: `${limits[0].meta.limit} (${deduplicatedLogRows.length} returned)`,
value: `${limitValue} (${deduplicatedLogRows.length} returned)`,
kind: LogsMetaKind.String,
});
}

View File

@ -13,7 +13,7 @@ import {
sortLogsResult,
buildQueryTransaction,
} from './explore';
import { ExploreUrlState, ExploreMode } from 'app/types/explore';
import { ExploreUrlState } from 'app/types/explore';
import store from 'app/core/store';
import {
DataQueryError,
@ -22,6 +22,7 @@ import {
LogLevel,
dateTime,
MutableDataFrame,
ExploreMode,
LogRowModel,
} from '@grafana/data';
import { RefreshPicker } from '@grafana/ui';

View File

@ -21,6 +21,7 @@ import {
TimeRange,
TimeZone,
toUtc,
ExploreMode,
} from '@grafana/data';
import { renderUrl } from 'app/core/utils/url';
import store from 'app/core/store';
@ -28,7 +29,7 @@ import kbn from 'app/core/utils/kbn';
import { getNextRefIdChar } from './query';
// Types
import { RefreshPicker } from '@grafana/ui';
import { ExploreMode, ExploreUrlState, QueryOptions, QueryTransaction } from 'app/types/explore';
import { ExploreUrlState, QueryOptions, QueryTransaction } from 'app/types/explore';
import { config } from '../config';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { DataSourceSrv } from '@grafana/runtime';

View File

@ -36,16 +36,10 @@ import {
TimeZone,
AbsoluteTimeRange,
LoadingState,
ExploreMode,
} from '@grafana/data';
import {
ExploreItemState,
ExploreUrlState,
ExploreId,
ExploreUpdateState,
ExploreUIState,
ExploreMode,
} from 'app/types/explore';
import { ExploreItemState, ExploreUrlState, ExploreId, ExploreUpdateState, ExploreUIState } from 'app/types/explore';
import { StoreState } from 'app/types';
import {
ensureQueries,

View File

@ -6,9 +6,9 @@ import memoizeOne from 'memoize-one';
import classNames from 'classnames';
import { css } from 'emotion';
import { ExploreId, ExploreItemState, ExploreMode } from 'app/types/explore';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { ToggleButtonGroup, ToggleButton, Tooltip, ButtonSelect, SetInterval } from '@grafana/ui';
import { RawTimeRange, TimeZone, TimeRange, DataQuery } from '@grafana/data';
import { RawTimeRange, TimeZone, TimeRange, DataQuery, ExploreMode } from '@grafana/data';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
import { StoreState } from 'app/types/store';
import {

View File

@ -20,9 +20,10 @@ import {
TimeRange,
AbsoluteTimeRange,
LoadingState,
ExploreMode,
} from '@grafana/data';
import { ExploreItemState, ExploreId, ExploreMode } from 'app/types/explore';
import { ExploreItemState, ExploreId } from 'app/types/explore';
import { Emitter } from 'app/core/utils/emitter';
import { highlightLogsExpressionAction, removeQueryRowAction } from './state/actionTypes';
@ -148,6 +149,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> {
onChange={this.onChange}
data={queryResponse}
absoluteRange={absoluteRange}
exploreMode={mode}
/>
) : (
<QueryEditor

View File

@ -13,8 +13,9 @@ import {
PanelData,
QueryFixAction,
TimeRange,
ExploreMode,
} from '@grafana/data';
import { ExploreId, ExploreItemState, ExploreMode, ExploreUIState } from 'app/types/explore';
import { ExploreId, ExploreItemState, ExploreUIState } from 'app/types/explore';
export interface AddQueryRowPayload {
exploreId: ExploreId;

View File

@ -1,9 +1,9 @@
import { PayloadAction } from '@reduxjs/toolkit';
import { DataQuery, DefaultTimeZone, LogsDedupStrategy, RawTimeRange, toUtc } from '@grafana/data';
import { DataQuery, DefaultTimeZone, LogsDedupStrategy, RawTimeRange, toUtc, ExploreMode } from '@grafana/data';
import * as Actions from './actions';
import { changeDatasource, loadDatasource, navigateToExplore, refreshExplore } from './actions';
import { ExploreId, ExploreMode, ExploreUpdateState, ExploreUrlState } from 'app/types';
import { ExploreId, ExploreUpdateState, ExploreUrlState } from 'app/types';
import { thunkTester } from 'test/core/thunk/thunkTester';
import {
initializeExploreAction,

View File

@ -16,6 +16,7 @@ import {
QueryFixAction,
RawTimeRange,
TimeRange,
ExploreMode,
} from '@grafana/data';
// Services & Utils
import store from 'app/core/store';
@ -40,7 +41,7 @@ import {
// Types
import { ExploreItemState, ExploreUrlState, ThunkResult } from 'app/types';
import { ExploreId, ExploreMode, ExploreUIState, QueryOptions } from 'app/types/explore';
import { ExploreId, ExploreUIState, QueryOptions } from 'app/types/explore';
import {
addQueryRowAction,
changeModeAction,

View File

@ -4,8 +4,9 @@ import {
dateTime,
LoadingState,
LogsDedupStrategy,
RawTimeRange,
toDataFrame,
ExploreMode,
RawTimeRange,
} from '@grafana/data';
import {
@ -16,7 +17,7 @@ import {
makeExploreItemState,
makeInitialUpdateState,
} from './reducers';
import { ExploreId, ExploreItemState, ExploreMode, ExploreState, ExploreUrlState } from 'app/types/explore';
import { ExploreId, ExploreItemState, ExploreState, ExploreUrlState } from 'app/types/explore';
import { reducerTester } from 'test/core/redux/reducerTester';
import {
changeModeAction,

View File

@ -11,6 +11,7 @@ import {
PanelEvents,
TimeZone,
toLegacyResponseData,
ExploreMode,
} from '@grafana/data';
import { RefreshPicker } from '@grafana/ui';
import { LocationUpdate } from '@grafana/runtime';
@ -25,7 +26,7 @@ import {
sortLogsResult,
stopQueryState,
} from 'app/core/utils/explore';
import { ExploreId, ExploreItemState, ExploreMode, ExploreState, ExploreUpdateState } from 'app/types/explore';
import { ExploreId, ExploreItemState, ExploreState, ExploreUpdateState } from 'app/types/explore';
import {
addQueryRowAction,
changeLoadingStateAction,

View File

@ -14,9 +14,9 @@ jest.mock('@grafana/data/src/datetime/moment_wrapper', () => ({
}));
import { ResultProcessor } from './ResultProcessor';
import { ExploreItemState, ExploreMode } from 'app/types/explore';
import { ExploreItemState } from 'app/types/explore';
import TableModel from 'app/core/table_model';
import { TimeSeries, LogRowModel, toDataFrame, FieldType } from '@grafana/data';
import { TimeSeries, LogRowModel, toDataFrame, FieldType, ExploreMode } from '@grafana/data';
const testContext = (options: any = {}) => {
const timeSeries = toDataFrame({

View File

@ -6,8 +6,9 @@ import {
TimeZone,
toDataFrame,
getDisplayProcessor,
ExploreMode,
} from '@grafana/data';
import { ExploreItemState, ExploreMode } from 'app/types/explore';
import { ExploreItemState } from 'app/types/explore';
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
import { sortLogsResult, refreshIntervalToSortOrder } from 'app/core/utils/explore';
import { dataFrameToLogsModel } from 'app/core/logs_model';

View File

@ -1,8 +1,7 @@
import React, { PureComponent } from 'react';
import { shuffle } from 'lodash';
import { ExploreStartPageProps, DataQuery } from '@grafana/data';
import { ExploreStartPageProps, DataQuery, ExploreMode } from '@grafana/data';
import LokiLanguageProvider from '../language_provider';
import { ExploreMode } from 'app/types';
const DEFAULT_EXAMPLES = ['{job="default/prometheus"}'];
const PREFERRED_LABELS = ['job', 'app', 'k8s_app'];

View File

@ -0,0 +1,32 @@
import React from 'react';
import { shallow } from 'enzyme';
import { LokiExploreExtraField, LokiExploreExtraFieldProps } from './LokiExploreExtraField';
const setup = (propOverrides?: LokiExploreExtraFieldProps) => {
const label = 'Loki Explore Extra Field';
const value = '123';
const type = 'number';
const min = 0;
const onChangeFunc = jest.fn();
const onKeyDownFunc = jest.fn();
const props: any = {
label,
value,
type,
min,
onChangeFunc,
onKeyDownFunc,
};
Object.assign(props, propOverrides);
return shallow(<LokiExploreExtraField {...props} />);
};
describe('LokiExploreExtraField', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,37 @@
// Libraries
import React, { memo } from 'react';
// Types
import { FormLabel } from '@grafana/ui';
export interface LokiExploreExtraFieldProps {
label: string;
onChangeFunc: (e: React.SyntheticEvent<HTMLInputElement>) => void;
onKeyDownFunc: (e: React.KeyboardEvent<HTMLInputElement>) => void;
value: string;
type?: string;
min?: number;
}
export function LokiExploreExtraField(props: LokiExploreExtraFieldProps) {
const { label, onChangeFunc, onKeyDownFunc, value, type, min } = props;
return (
<div className="gf-form-inline explore-input--ml">
<div className="gf-form">
<FormLabel width={6}>{label}</FormLabel>
<input
type={type}
className="gf-form-input width-6"
placeholder={'auto'}
onChange={onChangeFunc}
onKeyDown={onKeyDownFunc}
min={min}
value={value}
/>
</div>
</div>
);
}
export default memo(LokiExploreExtraField);

View File

@ -0,0 +1,95 @@
import React from 'react';
import { shallow, mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import LokiExploreQueryEditor from './LokiExploreQueryEditor';
import { LokiExploreExtraField } from './LokiExploreExtraField';
import { LokiDatasource } from '../datasource';
import { LokiQuery } from '../types';
import { ExploreMode, PanelData, LoadingState, dateTime } from '@grafana/data';
import { makeMockLokiDatasource } from '../mocks';
import LokiLanguageProvider from '../language_provider';
const setup = (renderMethod: any, propOverrides?: object) => {
const datasource: LokiDatasource = makeMockLokiDatasource({});
datasource.languageProvider = new LokiLanguageProvider(datasource);
const onRunQuery = jest.fn();
const onChange = jest.fn();
const query: LokiQuery = { expr: '', refId: 'A', maxLines: 0 };
const data: PanelData = {
state: LoadingState.NotStarted,
series: [],
request: {
requestId: '1',
dashboardId: 1,
interval: '1s',
panelId: 1,
range: {
from: dateTime('2020-01-01', 'YYYY-MM-DD'),
to: dateTime('2020-01-02', 'YYYY-MM-DD'),
raw: {
from: dateTime('2020-01-01', 'YYYY-MM-DD'),
to: dateTime('2020-01-02', 'YYYY-MM-DD'),
},
},
scopedVars: {},
targets: [],
timezone: 'GMT',
app: 'Grafana',
startTime: 0,
},
timeRange: {
from: dateTime('2020-01-01', 'YYYY-MM-DD'),
to: dateTime('2020-01-02', 'YYYY-MM-DD'),
raw: {
from: dateTime('2020-01-01', 'YYYY-MM-DD'),
to: dateTime('2020-01-02', 'YYYY-MM-DD'),
},
},
};
const history: any[] = [];
const exploreMode: ExploreMode = ExploreMode.Logs;
const props: any = {
query,
data,
datasource,
exploreMode,
history,
onChange,
onRunQuery,
};
Object.assign(props, { ...props, ...propOverrides });
return renderMethod(<LokiExploreQueryEditor {...props} />);
};
describe('LokiExploreQueryEditor', () => {
let originalGetSelection: typeof window.getSelection;
beforeAll(() => {
originalGetSelection = window.getSelection;
window.getSelection = () => null;
});
afterAll(() => {
window.getSelection = originalGetSelection;
});
it('should render component', () => {
const wrapper = setup(shallow);
expect(wrapper).toMatchSnapshot();
});
it('should render LokiQueryField with ExtraFieldElement when ExploreMode is set to Logs', async () => {
await act(async () => {
const wrapper = setup(mount);
expect(wrapper.find(LokiExploreExtraField).length).toBe(1);
});
});
it('should render LokiQueryField with no ExtraFieldElement when ExploreMode is not Logs', async () => {
await act(async () => {
const wrapper = setup(mount, { exploreMode: ExploreMode.Metrics });
expect(wrapper.find(LokiExploreExtraField).length).toBe(0);
});
});
});

View File

@ -0,0 +1,89 @@
// Libraries
import React, { memo } from 'react';
import _ from 'lodash';
// Types
import { AbsoluteTimeRange, ExploreQueryFieldProps, ExploreMode } from '@grafana/data';
import { LokiDatasource } from '../datasource';
import { LokiQuery, LokiOptions } from '../types';
import { LokiQueryField } from './LokiQueryField';
import LokiExploreExtraField from './LokiExploreExtraField';
type Props = ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions>;
export function LokiExploreQueryEditor(props: Props) {
const { query, data, datasource, exploreMode, history, onChange, onRunQuery } = props;
let absolute: AbsoluteTimeRange;
if (data && !_.isEmpty(data.request)) {
const { range } = data.request;
absolute = {
from: range.from.valueOf(),
to: range.to.valueOf(),
};
} else {
absolute = {
from: Date.now() - 10000,
to: Date.now(),
};
}
function onChangeQueryLimit(value: string) {
const { query, onChange } = props;
const nextQuery = { ...query, maxLines: preprocessMaxLines(value) };
onChange(nextQuery);
}
function preprocessMaxLines(value: string): number {
if (value.length === 0) {
// empty input - falls back to dataSource.maxLines limit
return NaN;
} else if (value.length > 0 && (isNaN(+value) || +value < 0)) {
// input with at least 1 character and that is either incorrect (value in the input field is not a number) or negative
// falls back to the limit of 0 lines
return 0;
} else {
// default case - correct input
return +value;
}
}
function onMaxLinesChange(e: React.SyntheticEvent<HTMLInputElement>) {
if (query.maxLines !== preprocessMaxLines(e.currentTarget.value)) {
onChangeQueryLimit(e.currentTarget.value);
}
}
function onReturnKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') {
onRunQuery();
}
}
return (
<LokiQueryField
datasource={datasource}
query={query}
onChange={onChange}
onRunQuery={onRunQuery}
history={history}
data={data}
absoluteRange={absolute}
ExtraFieldElement={
exploreMode === ExploreMode.Logs ? (
<LokiExploreExtraField
label={'Line limit'}
onChangeFunc={onMaxLinesChange}
onKeyDownFunc={onReturnKeyDown}
value={query?.maxLines?.toString() || ''}
type={'number'}
min={0}
/>
) : null
}
/>
);
}
export default memo(LokiExploreQueryEditor);

View File

@ -1,5 +1,5 @@
// Libraries
import React from 'react';
import React, { ReactNode } from 'react';
import {
ButtonCascader,
@ -69,6 +69,7 @@ export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps<LokiData
absoluteRange: AbsoluteTimeRange;
onLoadOptions: (selectedOptions: CascaderOption[]) => void;
onLabelsRefresh?: () => void;
ExtraFieldElement?: ReactNode;
}
export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormProps> {
@ -134,7 +135,16 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
};
render() {
const { data, query, syntaxLoaded, logLabelOptions, onLoadOptions, onLabelsRefresh, datasource } = this.props;
const {
ExtraFieldElement,
data,
query,
syntaxLoaded,
logLabelOptions,
onLoadOptions,
onLabelsRefresh,
datasource,
} = this.props;
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider;
const cleanText = datasource.languageProvider ? lokiLanguageProvider.cleanText : undefined;
const hasLogLabels = logLabelOptions && logLabelOptions.length > 0;
@ -144,8 +154,8 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
return (
<>
<div className="gf-form-inline">
<div className="gf-form">
<div className="gf-form-inline gf-form-inline--nowrap flex-grow-1">
<div className="gf-form flex-shrink-0">
<ButtonCascader
options={logLabelOptions || []}
disabled={buttonDisabled}
@ -156,7 +166,7 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
{chooserText}
</ButtonCascader>
</div>
<div className="gf-form gf-form--grow">
<div className="gf-form gf-form--grow flex-shrink-1">
<QueryField
additionalPlugins={this.plugins}
cleanText={cleanText}
@ -171,8 +181,13 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
syntaxLoaded={syntaxLoaded}
/>
</div>
{ExtraFieldElement}
</div>
<div>{showError ? <div className="prom-query-field-info text-error">{data.error.message}</div> : null}</div>
{showError ? (
<div className="query-row-break">
<div className="prom-query-field-info text-error">{data.error.message}</div>
</div>
) : null}
</>
);
}

View File

@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LokiExploreExtraField should render component 1`] = `
<div
className="gf-form-inline explore-input--ml"
>
<div
className="gf-form"
>
<Component
width={6}
>
Loki Explore Extra Field
</Component>
<input
className="gf-form-input width-6"
min={0}
onChange={[MockFunction]}
onKeyDown={[MockFunction]}
placeholder="auto"
type="number"
value="123"
/>
</div>
</div>
`;

View File

@ -0,0 +1,80 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LokiExploreQueryEditor should render component 1`] = `
<Component
ExtraFieldElement={
<Memo(LokiExploreExtraField)
label="Line limit"
min={0}
onChangeFunc={[Function]}
onKeyDownFunc={[Function]}
type="number"
value="0"
/>
}
absoluteRange={
Object {
"from": 1577836800000,
"to": 1577923200000,
}
}
data={
Object {
"request": Object {
"app": "Grafana",
"dashboardId": 1,
"interval": "1s",
"panelId": 1,
"range": Object {
"from": "2020-01-01T00:00:00.000Z",
"raw": Object {
"from": "2020-01-01T00:00:00.000Z",
"to": "2020-01-02T00:00:00.000Z",
},
"to": "2020-01-02T00:00:00.000Z",
},
"requestId": "1",
"scopedVars": Object {},
"startTime": 0,
"targets": Array [],
"timezone": "GMT",
},
"series": Array [],
"state": "NotStarted",
"timeRange": Object {
"from": "2020-01-01T00:00:00.000Z",
"raw": Object {
"from": "2020-01-01T00:00:00.000Z",
"to": "2020-01-02T00:00:00.000Z",
},
"to": "2020-01-02T00:00:00.000Z",
},
}
}
datasource={
Object {
"languageProvider": LokiLanguageProvider {
"cleanText": [Function],
"datasource": [Circular],
"getBeginningCompletionItems": [Function],
"getTermCompletionItems": [Function],
"labelKeys": Object {},
"labelValues": Object {},
"request": [Function],
"start": [Function],
},
"metadataRequest": [Function],
}
}
history={Array []}
onChange={[MockFunction]}
onRunQuery={[MockFunction]}
query={
Object {
"expr": "",
"maxLines": 0,
"refId": "A",
}
}
/>
`;

View File

@ -1,11 +1,18 @@
import LokiDatasource, { RangeQueryOptions } from './datasource';
import { LokiQuery, LokiResultType, LokiResponse, LokiLegacyStreamResponse } from './types';
import { getQueryOptions } from 'test/helpers/getQueryOptions';
import { AnnotationQueryRequest, DataSourceApi, DataFrame, dateTime, TimeRange, FieldCache } from '@grafana/data';
import {
AnnotationQueryRequest,
DataSourceApi,
DataFrame,
dateTime,
TimeRange,
ExploreMode,
FieldCache,
} from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { CustomVariable } from 'app/features/templating/custom_variable';
import { makeMockLokiDatasource } from './mocks';
import { ExploreMode } from 'app/types';
import { of } from 'rxjs';
import omit from 'lodash/omit';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
@ -478,7 +485,7 @@ function makeLimitTest(instanceSettings: any, datasourceRequestMock: any, templa
const ds = new LokiDatasource(settings, templateSrvMock);
datasourceRequestMock.mockImplementation(() => Promise.resolve(testResp));
const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B', maxLines: maxDataPoints }] });
if (Number.isFinite(maxDataPoints)) {
options.maxDataPoints = maxDataPoints;
} else {

View File

@ -35,6 +35,7 @@ import {
DataQueryRequest,
DataQueryResponse,
AnnotationQueryRequest,
ExploreMode,
ScopedVars,
} from '@grafana/data';
@ -48,7 +49,6 @@ import {
LokiRangeQueryRequest,
LokiStreamResponse,
} from './types';
import { ExploreMode } from 'app/types';
import { LegacyTarget, LiveStreams } from './live_streams';
import LanguageProvider from './language_provider';
@ -267,25 +267,45 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
options: RangeQueryOptions,
responseListLength = 1
): Observable<DataQueryResponse> => {
if (target.liveStreaming) {
return this.runLiveQuery(target, options);
// target.maxLines value already preprocessed
// available cases:
// 1) empty input -> mapped to NaN, falls back to dataSource.maxLines limit
// 2) input with at least 1 character and that is either incorrect (value in the input field is not a number) or negative
// - mapped to 0, falls back to the limit of 0 lines
// 3) default case - correct input, mapped to the value from the input field
let linesLimit = 0;
if (target.maxLines === undefined) {
// no target.maxLines, using options.maxDataPoints
linesLimit = Math.min(options.maxDataPoints || Infinity, this.maxLines);
} else {
// using target.maxLines
if (isNaN(target.maxLines)) {
linesLimit = this.maxLines;
} else {
linesLimit = target.maxLines;
}
}
const query = this.createRangeQuery(target, options);
const queryOptions = { ...options, maxDataPoints: linesLimit };
if (target.liveStreaming) {
return this.runLiveQuery(target, queryOptions);
}
const query = this.createRangeQuery(target, queryOptions);
return this._request(RANGE_QUERY_ENDPOINT, query).pipe(
catchError((err: any) => this.throwUnless(err, err.cancelled || err.status === 404, target)),
filter((response: any) => (response.cancelled ? false : true)),
switchMap((response: { data: LokiResponse; status: number }) =>
iif<DataQueryResponse, DataQueryResponse>(
() => response.status === 404,
defer(() => this.runLegacyQuery(target, options)),
defer(() => this.runLegacyQuery(target, queryOptions)),
defer(() =>
processRangeQueryResponse(
response.data,
target,
query,
responseListLength,
this.maxLines,
linesLimit,
this.instanceSettings.jsonData,
options.reverse
)

View File

@ -2,7 +2,7 @@ import { DataSourcePlugin } from '@grafana/data';
import Datasource from './datasource';
import LokiCheatSheet from './components/LokiCheatSheet';
import LokiQueryField from './components/LokiQueryField';
import LokiExploreQueryEditor from './components/LokiExploreQueryEditor';
import LokiQueryEditor from './components/LokiQueryEditor';
import { LokiAnnotationsQueryCtrl } from './LokiAnnotationsQueryCtrl';
import { ConfigEditor } from './configuration/ConfigEditor';
@ -10,6 +10,6 @@ import { ConfigEditor } from './configuration/ConfigEditor';
export const plugin = new DataSourcePlugin(Datasource)
.setQueryEditor(LokiQueryEditor)
.setConfigEditor(ConfigEditor)
.setExploreQueryField(LokiQueryField)
.setExploreQueryField(LokiExploreQueryEditor)
.setExploreStartPage(LokiCheatSheet)
.setAnnotationQueryCtrl(LokiAnnotationsQueryCtrl);

View File

@ -467,7 +467,7 @@ export function processRangeQueryResponse(
switch (response.data.resultType) {
case LokiResultType.Stream:
return of({
data: lokiStreamsToDataframes(response.data.result, target, limit, config, reverse),
data: lokiStreamsToDataframes(limit > 0 ? response.data.result : [], target, limit, config, reverse),
key: `${target.refId}_log`,
});

View File

@ -14,15 +14,11 @@ import {
AbsoluteTimeRange,
GraphSeriesXY,
DataFrame,
ExploreMode,
} from '@grafana/data';
import { Emitter } from 'app/core/core';
export enum ExploreMode {
Metrics = 'Metrics',
Logs = 'Logs',
}
export enum ExploreId {
left = 'left',
right = 'right',