mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
9b9f1ad1b9
commit
df48d1c19f
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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({
|
||||
|
@ -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';
|
||||
|
@ -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'];
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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);
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
`;
|
@ -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",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
@ -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 {
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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);
|
||||
|
@ -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`,
|
||||
});
|
||||
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user