mirror of https://github.com/grafana/grafana
Loki: Implement hints for query builder (#51795)
* Loki: Implement hints for query builder
* Update name of file
* Update imports
* Refactor
* Remove unused import
* Unify
* Revert "Unify"
This reverts commit 78da0e27e3
.
* Unify
* Fix types
* Fix tests
* Fix type error
* Simplify
* Update test
* Add documentation
* Update comment
* Add tests for addParserToQuery
* Smaller updates
pull/51918/head
parent
2b2c09b8d5
commit
10cb84e401
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,73 @@ |
||||
import { ArrayVector, DataFrame, FieldType } from '@grafana/data'; |
||||
|
||||
import { getQueryHints } from './queryHints'; |
||||
|
||||
describe('getQueryHints', () => { |
||||
describe('when series with json logs', () => { |
||||
const jsonSeries: DataFrame = { |
||||
name: 'logs', |
||||
length: 2, |
||||
fields: [ |
||||
{ |
||||
name: 'Line', |
||||
type: FieldType.string, |
||||
config: {}, |
||||
values: new ArrayVector(['{"foo": "bar", "bar": "baz"}', '{"foo": "bar", "bar": "baz"}']), |
||||
}, |
||||
], |
||||
}; |
||||
it('suggest json parser when no parser in query', () => { |
||||
expect(getQueryHints('{job="grafana"', [jsonSeries])).toMatchObject([{ type: 'ADD_JSON_PARSER' }]); |
||||
}); |
||||
it('does not suggest parser when parser in query', () => { |
||||
expect(getQueryHints('{job="grafana" | json', [jsonSeries])).toEqual([]); |
||||
}); |
||||
}); |
||||
|
||||
describe('when series with logfmt logs', () => { |
||||
const logfmtSeries: DataFrame = { |
||||
name: 'logs', |
||||
length: 2, |
||||
fields: [ |
||||
{ |
||||
name: 'Line', |
||||
type: FieldType.string, |
||||
config: {}, |
||||
values: new ArrayVector(['foo="bar" bar="baz"', 'foo="bar" bar="baz"']), |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
it('suggest logfmt parser when no parser in query', () => { |
||||
expect(getQueryHints('{job="grafana"', [logfmtSeries])).toMatchObject([{ type: 'ADD_LOGFMT_PARSER' }]); |
||||
}); |
||||
it('does not suggest parser when parser in query', () => { |
||||
expect(getQueryHints('{job="grafana" | json', [logfmtSeries])).toEqual([]); |
||||
}); |
||||
}); |
||||
|
||||
describe('when series with json and logfmt logs', () => { |
||||
const jsonAndLogfmtSeries: DataFrame = { |
||||
name: 'logs', |
||||
length: 2, |
||||
fields: [ |
||||
{ |
||||
name: 'Line', |
||||
type: FieldType.string, |
||||
config: {}, |
||||
values: new ArrayVector(['{"foo": "bar", "bar": "baz"}', 'foo="bar" bar="baz"']), |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
it('suggest logfmt parser when no parser in query', () => { |
||||
expect(getQueryHints('{job="grafana"', [jsonAndLogfmtSeries])).toMatchObject([ |
||||
{ type: 'ADD_JSON_PARSER' }, |
||||
{ type: 'ADD_LOGFMT_PARSER' }, |
||||
]); |
||||
}); |
||||
it('does not suggest parser when parser in query', () => { |
||||
expect(getQueryHints('{job="grafana" | json', [jsonAndLogfmtSeries])).toEqual([]); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,42 @@ |
||||
import { DataFrame, QueryHint } from '@grafana/data'; |
||||
|
||||
import { isQueryWithParser } from './query_utils'; |
||||
import { extractLogParserFromDataFrame } from './responseUtils'; |
||||
|
||||
export function getQueryHints(query: string, series: DataFrame[]): QueryHint[] { |
||||
const hints: QueryHint[] = []; |
||||
if (series.length > 0) { |
||||
const { hasLogfmt, hasJSON } = extractLogParserFromDataFrame(series[0]); |
||||
const queryWithParser = isQueryWithParser(query); |
||||
|
||||
if (hasJSON && !queryWithParser) { |
||||
hints.push({ |
||||
type: 'ADD_JSON_PARSER', |
||||
label: 'Selected log stream selector has JSON formatted logs.', |
||||
fix: { |
||||
label: 'Consider using JSON parser.', |
||||
action: { |
||||
type: 'ADD_JSON_PARSER', |
||||
query, |
||||
}, |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
if (hasLogfmt && !queryWithParser) { |
||||
hints.push({ |
||||
type: 'ADD_LOGFMT_PARSER', |
||||
label: 'Selected log stream selector has logfmt formatted logs.', |
||||
fix: { |
||||
label: 'Consider using logfmt parser.', |
||||
action: { |
||||
type: 'ADD_LOGFMT_PARSER', |
||||
query, |
||||
}, |
||||
}, |
||||
}); |
||||
} |
||||
} |
||||
|
||||
return hints; |
||||
} |
@ -1,6 +1,29 @@ |
||||
import { DataFrame, Labels } from '@grafana/data'; |
||||
import { DataFrame, FieldType, getParser, Labels, LogsParsers } from '@grafana/data'; |
||||
|
||||
export function dataFrameHasLokiError(frame: DataFrame): boolean { |
||||
const labelSets: Labels[] = frame.fields.find((f) => f.name === 'labels')?.values.toArray() ?? []; |
||||
return labelSets.some((labels) => labels.__error__ !== undefined); |
||||
} |
||||
export function extractLogParserFromDataFrame(frame: DataFrame): { hasLogfmt: boolean; hasJSON: boolean } { |
||||
const lineField = frame.fields.find((field) => field.type === FieldType.string); |
||||
if (lineField == null) { |
||||
return { hasJSON: false, hasLogfmt: false }; |
||||
} |
||||
|
||||
const logLines: string[] = lineField.values.toArray(); |
||||
|
||||
let hasJSON = false; |
||||
let hasLogfmt = false; |
||||
|
||||
logLines.forEach((line) => { |
||||
const parser = getParser(line); |
||||
if (parser === LogsParsers.JSON) { |
||||
hasJSON = true; |
||||
} |
||||
if (parser === LogsParsers.logfmt) { |
||||
hasLogfmt = true; |
||||
} |
||||
}); |
||||
|
||||
return { hasLogfmt, hasJSON }; |
||||
} |
||||
|
Loading…
Reference in new issue