mirror of https://github.com/grafana/grafana
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 testspull/21972/head
parent
9b9f1ad1b9
commit
df48d1c19f
@ -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); |
||||
@ -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", |
||||
} |
||||
} |
||||
/> |
||||
`; |
||||
Loading…
Reference in new issue