From df48d1c19f35f3de02809247cfc3d15145f8d226 Mon Sep 17 00:00:00 2001 From: Lukas Siatka Date: Thu, 6 Feb 2020 12:34:52 +0000 Subject: [PATCH] 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 --- packages/grafana-data/src/types/datasource.ts | 15 ++- public/app/core/logs_model.ts | 8 +- public/app/core/utils/explore.test.ts | 3 +- public/app/core/utils/explore.ts | 3 +- public/app/features/explore/Explore.tsx | 10 +- .../app/features/explore/ExploreToolbar.tsx | 4 +- public/app/features/explore/QueryRow.tsx | 4 +- .../app/features/explore/state/actionTypes.ts | 3 +- .../features/explore/state/actions.test.ts | 4 +- public/app/features/explore/state/actions.ts | 3 +- .../features/explore/state/reducers.test.ts | 5 +- public/app/features/explore/state/reducers.ts | 3 +- .../explore/utils/ResultProcessor.test.ts | 4 +- .../features/explore/utils/ResultProcessor.ts | 3 +- .../loki/components/LokiCheatSheet.tsx | 3 +- .../components/LokiExploreExtraField.test.tsx | 32 +++++++ .../loki/components/LokiExploreExtraField.tsx | 37 ++++++++ .../LokiExploreQueryEditor.test.tsx | 95 +++++++++++++++++++ .../components/LokiExploreQueryEditor.tsx | 89 +++++++++++++++++ .../loki/components/LokiQueryFieldForm.tsx | 27 ++++-- .../LokiExploreExtraField.test.tsx.snap | 26 +++++ .../LokiExploreQueryEditor.test.tsx.snap | 80 ++++++++++++++++ .../datasource/loki/datasource.test.ts | 13 ++- .../app/plugins/datasource/loki/datasource.ts | 32 +++++-- public/app/plugins/datasource/loki/module.ts | 4 +- .../datasource/loki/result_transformer.ts | 2 +- public/app/types/explore.ts | 6 +- 27 files changed, 467 insertions(+), 51 deletions(-) create mode 100644 public/app/plugins/datasource/loki/components/LokiExploreExtraField.test.tsx create mode 100644 public/app/plugins/datasource/loki/components/LokiExploreExtraField.tsx create mode 100644 public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx create mode 100644 public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx create mode 100644 public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreExtraField.test.tsx.snap create mode 100644 public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 707bf32b701..c52f1c04c79 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -312,6 +312,11 @@ export enum DataSourceStatus { Disconnected, } +export enum ExploreMode { + Logs = 'Logs', + Metrics = 'Metrics', +} + export interface ExploreQueryFieldProps< DSType extends DataSourceApi, 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 { app: CoreApp | string; cacheTimeout?: string; - exploreMode?: 'Logs' | 'Metrics'; + exploreMode?: ExploreMode; rangeRaw?: RawTimeRange; timeInfo?: string; // The query time description (blue text in the upper right) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index b1d7727deb5..b3964fee145 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -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, }); } diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 5509029061b..1f3dcc3c01b 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -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'; diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index be2fe2f2162..6be1936e3ea 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -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'; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index c1f18d6ea87..4ee83f48020 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -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, diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index f055bf7a7b0..71c00b19e43 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -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 { diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index 299a35bbc13..cf9a311ed3e 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -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 { onChange={this.onChange} data={queryResponse} absoluteRange={absoluteRange} + exploreMode={mode} /> ) : ( ({ })); 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({ diff --git a/public/app/features/explore/utils/ResultProcessor.ts b/public/app/features/explore/utils/ResultProcessor.ts index 2ceef9b38a5..a8618a08161 100644 --- a/public/app/features/explore/utils/ResultProcessor.ts +++ b/public/app/features/explore/utils/ResultProcessor.ts @@ -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'; diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index 0a09e1e17a1..7957eb221ae 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -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']; diff --git a/public/app/plugins/datasource/loki/components/LokiExploreExtraField.test.tsx b/public/app/plugins/datasource/loki/components/LokiExploreExtraField.test.tsx new file mode 100644 index 00000000000..5e75d1da317 --- /dev/null +++ b/public/app/plugins/datasource/loki/components/LokiExploreExtraField.test.tsx @@ -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(); +}; + +describe('LokiExploreExtraField', () => { + it('should render component', () => { + const wrapper = setup(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/plugins/datasource/loki/components/LokiExploreExtraField.tsx b/public/app/plugins/datasource/loki/components/LokiExploreExtraField.tsx new file mode 100644 index 00000000000..cd9b4993489 --- /dev/null +++ b/public/app/plugins/datasource/loki/components/LokiExploreExtraField.tsx @@ -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) => void; + onKeyDownFunc: (e: React.KeyboardEvent) => void; + value: string; + type?: string; + min?: number; +} + +export function LokiExploreExtraField(props: LokiExploreExtraFieldProps) { + const { label, onChangeFunc, onKeyDownFunc, value, type, min } = props; + + return ( +
+
+ {label} + +
+
+ ); +} + +export default memo(LokiExploreExtraField); diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx new file mode 100644 index 00000000000..42d4e82946e --- /dev/null +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx @@ -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(); +}; + +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); + }); + }); +}); diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx new file mode 100644 index 00000000000..18b95bf4609 --- /dev/null +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx @@ -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; + +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) { + if (query.maxLines !== preprocessMaxLines(e.currentTarget.value)) { + onChangeQueryLimit(e.currentTarget.value); + } + } + + function onReturnKeyDown(e: React.KeyboardEvent) { + if (e.key === 'Enter') { + onRunQuery(); + } + } + + return ( + + ) : null + } + /> + ); +} + +export default memo(LokiExploreQueryEditor); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx index 28af42836a0..5b2db1dd0c4 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx @@ -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 void; onLabelsRefresh?: () => void; + ExtraFieldElement?: ReactNode; } export class LokiQueryFieldForm extends React.PureComponent { @@ -134,7 +135,16 @@ export class LokiQueryFieldForm extends React.PureComponent 0; @@ -144,8 +154,8 @@ export class LokiQueryFieldForm extends React.PureComponent -
-
+
+
-
+
+ {ExtraFieldElement}
-
{showError ?
{data.error.message}
: null}
+ {showError ? ( +
+
{data.error.message}
+
+ ) : null} ); } 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 new file mode 100644 index 00000000000..152aa117b28 --- /dev/null +++ b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreExtraField.test.tsx.snap @@ -0,0 +1,26 @@ +// 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 new file mode 100644 index 00000000000..c493b7cb444 --- /dev/null +++ b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LokiExploreQueryEditor should render component 1`] = ` + + } + 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", + } + } +/> +`; diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 5f735fb8632..38770b3db44 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -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({ targets: [{ expr: 'foo', refId: 'B' }] }); + const options = getQueryOptions({ targets: [{ expr: 'foo', refId: 'B', maxLines: maxDataPoints }] }); if (Number.isFinite(maxDataPoints)) { options.maxDataPoints = maxDataPoints; } else { diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 9083c769d77..6b2e02e7a3e 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -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 { options: RangeQueryOptions, responseListLength = 1 ): Observable => { - 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( () => 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 ) diff --git a/public/app/plugins/datasource/loki/module.ts b/public/app/plugins/datasource/loki/module.ts index 756408df662..126d0d80d58 100644 --- a/public/app/plugins/datasource/loki/module.ts +++ b/public/app/plugins/datasource/loki/module.ts @@ -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); diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 11e2caacd15..246fe8d3247 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -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`, }); diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 829f6551943..cd6b9bbb34a 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -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',