Loki Query Utils: Extract and refactor common functionality (#70185)

* Query utils: refactor isQueryPipelineErrorFiltering and getLogQueryFromMetricsQuery

* Query utils: refactor isQueryWithRangeVariable and getHighlighterExpressionsFromQuery

* Get parser: return log expression even if no pipelineExpr is present

* Update tests
pull/70461/head
Matias Chomicki 2 years ago committed by GitHub
parent cf4d86d95f
commit a489135825
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/situation.test.ts
  2. 23
      public/app/plugins/datasource/loki/queryUtils.test.ts
  3. 198
      public/app/plugins/datasource/loki/queryUtils.ts

@ -95,7 +95,7 @@ describe('situation', () => {
type: 'AFTER_SELECTOR', type: 'AFTER_SELECTOR',
afterPipe: true, afterPipe: true,
hasSpace: false, hasSpace: false,
logQuery: '{place="luna"}| logfmt |', logQuery: '{place="luna"} | logfmt |',
}); });
}); });
@ -179,7 +179,7 @@ describe('situation', () => {
'quantile_over_time(0.99, {cluster="ops-tools1",container="ingress-nginx"} | json | __error__ = "" | unwrap ^', 'quantile_over_time(0.99, {cluster="ops-tools1",container="ingress-nginx"} | json | __error__ = "" | unwrap ^',
{ {
type: 'AFTER_UNWRAP', type: 'AFTER_UNWRAP',
logQuery: '{cluster="ops-tools1",container="ingress-nginx"}| json | __error__ = ""', logQuery: '{cluster="ops-tools1",container="ingress-nginx"} | json | __error__ = ""',
} }
); );

@ -11,6 +11,8 @@ import {
requestSupportsSplitting, requestSupportsSplitting,
isQueryWithDistinct, isQueryWithDistinct,
isQueryWithRangeVariable, isQueryWithRangeVariable,
isQueryPipelineErrorFiltering,
getLogQueryFromMetricsQuery,
getNormalizedLokiQuery, getNormalizedLokiQuery,
} from './queryUtils'; } from './queryUtils';
import { LokiQuery, LokiQueryType } from './types'; import { LokiQuery, LokiQueryType } from './types';
@ -393,3 +395,24 @@ describe('requestSupportsSplitting', () => {
expect(requestSupportsSplitting(requests)).toBe(true); expect(requestSupportsSplitting(requests)).toBe(true);
}); });
}); });
describe('isQueryPipelineErrorFiltering', () => {
it('identifies pipeline error filters', () => {
expect(isQueryPipelineErrorFiltering('{job="grafana"} | logfmt | __error__=""')).toBe(true);
expect(isQueryPipelineErrorFiltering('{job="grafana"} | logfmt | error=""')).toBe(false);
});
});
describe('getLogQueryFromMetricsQuery', () => {
it('returns the log query from a metric query', () => {
expect(getLogQueryFromMetricsQuery('count_over_time({job="grafana"} | logfmt | label="value" [1m])')).toBe(
'{job="grafana"} | logfmt | label="value"'
);
expect(getLogQueryFromMetricsQuery('count_over_time({job="grafana"} [1m])')).toBe('{job="grafana"}');
expect(
getLogQueryFromMetricsQuery(
'sum(quantile_over_time(0.5, {label="$var"} | logfmt | __error__=`` | unwrap latency | __error__=`` [$__interval]))'
)
).toBe('{label="$var"} | logfmt | __error__=``');
});
});

@ -38,15 +38,7 @@ export function formatQuery(selector: string | undefined): string {
export function getHighlighterExpressionsFromQuery(input: string): string[] { export function getHighlighterExpressionsFromQuery(input: string): string[] {
const results = []; const results = [];
const tree = parser.parse(input); const filters = getNodesFromQuery(input, [LineFilter]);
const filters: SyntaxNode[] = [];
tree.iterate({
enter: ({ type, node }): void => {
if (type.id === LineFilter) {
filters.push(node);
}
},
});
for (let filter of filters) { for (let filter of filters) {
const pipeExact = filter.getChild(Filter)?.getChild(PipeExact); const pipeExact = filter.getChild(Filter)?.getChild(PipeExact);
@ -139,92 +131,73 @@ export function parseToNodeNamesArray(query: string): string[] {
return queryParts; return queryParts;
} }
export function isValidQuery(query: string): boolean { export function isQueryWithNode(query: string, nodeType: number): boolean {
let isValid = true; let isQueryWithNode = false;
const tree = parser.parse(query); const tree = parser.parse(query);
tree.iterate({ tree.iterate({
enter: ({ type }): false | void => { enter: ({ type }): false | void => {
if (type.id === ErrorId) { if (type.id === nodeType) {
isValid = false; isQueryWithNode = true;
return false;
} }
}, },
}); });
return isValid; return isQueryWithNode;
} }
export function isLogsQuery(query: string): boolean { export function getNodesFromQuery(query: string, nodeTypes: number[]): SyntaxNode[] {
let isLogsQuery = true; const nodes: SyntaxNode[] = [];
const tree = parser.parse(query); const tree = parser.parse(query);
tree.iterate({ tree.iterate({
enter: ({ type }): false | void => { enter: (node): false | void => {
if (type.id === MetricExpr) { if (nodeTypes.includes(node.type.id)) {
isLogsQuery = false; nodes.push(node.node);
} }
}, },
}); });
return isLogsQuery; return nodes;
}
export function getNodeFromQuery(query: string, nodeType: number): SyntaxNode | undefined {
const nodes = getNodesFromQuery(query, [nodeType]);
return nodes.length > 0 ? nodes[0] : undefined;
}
export function isValidQuery(query: string): boolean {
return !isQueryWithNode(query, ErrorId);
}
export function isLogsQuery(query: string): boolean {
return !isQueryWithNode(query, MetricExpr);
} }
export function isQueryWithParser(query: string): { queryWithParser: boolean; parserCount: number } { export function isQueryWithParser(query: string): { queryWithParser: boolean; parserCount: number } {
let parserCount = 0; const nodes = getNodesFromQuery(query, [LabelParser, JsonExpressionParser]);
const tree = parser.parse(query); const parserCount = nodes.length;
tree.iterate({
enter: ({ type }): false | void => {
if (type.id === LabelParser || type.id === JsonExpressionParser) {
parserCount++;
}
},
});
return { queryWithParser: parserCount > 0, parserCount }; return { queryWithParser: parserCount > 0, parserCount };
} }
export function getParserFromQuery(query: string): string | undefined { export function getParserFromQuery(query: string): string | undefined {
const tree = parser.parse(query); const parsers = getNodesFromQuery(query, [LabelParser, JsonExpressionParser]);
let logParser: string | undefined = undefined; return parsers.length > 0 ? query.substring(parsers[0].from, parsers[0].to).trim() : undefined;
tree.iterate({
enter: (node: SyntaxNode): false | void => {
if (node.type.id === LabelParser || node.type.id === JsonExpressionParser) {
logParser = query.substring(node.from, node.to).trim();
return false;
}
},
});
return logParser;
} }
export function isQueryPipelineErrorFiltering(query: string): boolean { export function isQueryPipelineErrorFiltering(query: string): boolean {
let isQueryPipelineErrorFiltering = false; const labels = getNodesFromQuery(query, [LabelFilter]);
const tree = parser.parse(query); for (const node of labels) {
tree.iterate({ const label = node.getChild(Matcher)?.getChild(Identifier);
enter: ({ type, node }): false | void => { if (label) {
if (type.id === LabelFilter) { const labelName = query.substring(label.from, label.to);
const label = node.getChild(Matcher)?.getChild(Identifier); if (labelName === '__error__') {
if (label) { return true;
const labelName = query.substring(label.from, label.to);
if (labelName === '__error__') {
isQueryPipelineErrorFiltering = true;
}
}
} }
}, }
}); }
return false;
return isQueryPipelineErrorFiltering;
} }
export function isQueryWithLabelFormat(query: string): boolean { export function isQueryWithLabelFormat(query: string): boolean {
let queryWithLabelFormat = false; return isQueryWithNode(query, LabelFormatExpr);
const tree = parser.parse(query);
tree.iterate({
enter: ({ type }): false | void => {
if (type.id === LabelFormatExpr) {
queryWithLabelFormat = true;
return false;
}
},
});
return queryWithLabelFormat;
} }
export function getLogQueryFromMetricsQuery(query: string): string { export function getLogQueryFromMetricsQuery(query: string): string {
@ -232,92 +205,39 @@ export function getLogQueryFromMetricsQuery(query: string): string {
return query; return query;
} }
const tree = parser.parse(query);
// Log query in metrics query composes of Selector & PipelineExpr // Log query in metrics query composes of Selector & PipelineExpr
let selector = ''; const selectorNode = getNodeFromQuery(query, Selector);
tree.iterate({ if (!selectorNode) {
enter: ({ type, from, to }): false | void => { return query;
if (type.id === Selector) { }
selector = query.substring(from, to); const selector = query.substring(selectorNode.from, selectorNode.to);
return false;
}
},
});
let pipelineExpr = ''; const pipelineExprNode = getNodeFromQuery(query, PipelineExpr);
tree.iterate({ const pipelineExpr = pipelineExprNode ? query.substring(pipelineExprNode.from, pipelineExprNode.to) : '';
enter: ({ type, from, to }): false | void => {
if (type.id === PipelineExpr) {
pipelineExpr = query.substring(from, to);
return false;
}
},
});
return selector + pipelineExpr; return `${selector} ${pipelineExpr}`.trim();
} }
export function isQueryWithLabelFilter(query: string): boolean { export function isQueryWithLabelFilter(query: string): boolean {
const tree = parser.parse(query); return isQueryWithNode(query, LabelFilter);
let hasLabelFilter = false;
tree.iterate({
enter: ({ type }): false | void => {
if (type.id === LabelFilter) {
hasLabelFilter = true;
return false;
}
},
});
return hasLabelFilter;
} }
export function isQueryWithLineFilter(query: string): boolean { export function isQueryWithLineFilter(query: string): boolean {
const tree = parser.parse(query); return isQueryWithNode(query, LineFilter);
let queryWithLineFilter = false;
tree.iterate({
enter: ({ type }): false | void => {
if (type.id === LineFilter) {
queryWithLineFilter = true;
return false;
}
},
});
return queryWithLineFilter;
} }
export function isQueryWithDistinct(query: string): boolean { export function isQueryWithDistinct(query: string): boolean {
let hasDistinct = false; return isQueryWithNode(query, Distinct);
const tree = parser.parse(query);
tree.iterate({
enter: ({ type }): false | void => {
if (type.id === Distinct) {
hasDistinct = true;
return false;
}
},
});
return hasDistinct;
} }
export function isQueryWithRangeVariable(query: string): boolean { export function isQueryWithRangeVariable(query: string): boolean {
let hasRangeVariableDuration = false; const rangeNodes = getNodesFromQuery(query, [Range]);
const tree = parser.parse(query); for (const node of rangeNodes) {
tree.iterate({ if (query.substring(node.from, node.to).match(/\[\$__range(_s|_ms)?/)) {
enter: ({ type, from, to }): false | void => { return true;
if (type.id === Range) { }
if (query.substring(from, to).match(/\[\$__range(_s|_ms)?/)) { }
hasRangeVariableDuration = true; return false;
return false;
}
}
},
});
return hasRangeVariableDuration;
} }
export function getStreamSelectorsFromQuery(query: string): string[] { export function getStreamSelectorsFromQuery(query: string): string[] {

Loading…
Cancel
Save