diff --git a/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts b/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts index 64ee292ad82..c3b94525519 100644 --- a/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts +++ b/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts @@ -67,6 +67,18 @@ describe('buildVisualQueryFromString', () => { ); }); + it('returns error for query with ip matching line filter', () => { + const context = buildVisualQueryFromString('{app="frontend"} |= ip("192.168.4.5/16")'); + expect(context.errors).toEqual([ + { + text: 'Matching ip addresses not supported in query builder: |= ip("192.168.4.5/16")', + from: 17, + to: 40, + parentType: 'LineFilters', + }, + ]); + }); + it('parses query with matcher label filter', () => { expect(buildVisualQueryFromString('{app="frontend"} | bar="baz"')).toEqual( noErrors({ @@ -127,6 +139,30 @@ describe('buildVisualQueryFromString', () => { ); }); + it('returns error for query with "and", "or", "comma" in label filter', () => { + const context = buildVisualQueryFromString('{app="frontend"} | logfmt | level="error" and job="grafana"'); + expect(context.errors).toEqual([ + { + text: 'Label filter with comma, "and", "or" not supported in query builder: level="error" and job="grafana"', + from: 28, + to: 59, + parentType: 'PipelineStage', + }, + ]); + }); + + it('returns error for query with ip label filter', () => { + const context = buildVisualQueryFromString('{app="frontend"} | logfmt | address=ip("192.168.4.5/16")'); + expect(context.errors).toEqual([ + { + text: 'IpLabelFilter not supported in query builder: address=ip("192.168.4.5/16")', + from: 28, + to: 56, + parentType: 'PipelineStage', + }, + ]); + }); + it('parses query with with parser', () => { expect(buildVisualQueryFromString('{app="frontend"} | json')).toEqual( noErrors({ @@ -142,6 +178,18 @@ describe('buildVisualQueryFromString', () => { ); }); + it('returns error for query with JSON expression parser', () => { + const context = buildVisualQueryFromString('{app="frontend"} | json label="value" '); + expect(context.errors).toEqual([ + { + text: 'JsonExpressionParser not supported in visual query builder: json label="value"', + from: 19, + to: 37, + parentType: 'PipelineStage', + }, + ]); + }); + it('parses query with with simple unwrap', () => { expect( buildVisualQueryFromString('sum_over_time({app="frontend"} | logfmt | unwrap bytes_processed [1m])') @@ -163,6 +211,20 @@ describe('buildVisualQueryFromString', () => { ); }); + it('returns error for query with unwrap and conversion operation', () => { + const context = buildVisualQueryFromString( + 'sum_over_time({app="frontend"} | logfmt | unwrap duration(label) [5m])' + ); + expect(context.errors).toEqual([ + { + text: 'Unwrap with conversion operator not supported in query builder: | unwrap duration(label)', + from: 40, + to: 64, + parentType: 'LogRangeExpr', + }, + ]); + }); + it('parses metrics query with function', () => { expect(buildVisualQueryFromString('rate({app="frontend"} | json [5m])')).toEqual( noErrors({ @@ -323,7 +385,7 @@ describe('buildVisualQueryFromString', () => { label: 'app', }, ], - operations: [{ id: 'label_format', params: ['newLabel', '=', 'oldLabel'] }], + operations: [{ id: 'label_format', params: ['newLabel', 'oldLabel'] }], }) ); }); @@ -339,8 +401,8 @@ describe('buildVisualQueryFromString', () => { }, ], operations: [ - { id: 'label_format', params: ['newLabel', '=', 'oldLabel'] }, - { id: 'label_format', params: ['bar', '=', 'baz'] }, + { id: 'label_format', params: ['newLabel', 'oldLabel'] }, + { id: 'label_format', params: ['bar', 'baz'] }, ], }) ); diff --git a/public/app/plugins/datasource/loki/querybuilder/parsing.ts b/public/app/plugins/datasource/loki/querybuilder/parsing.ts index 67c6b3cec10..3b19dbfe863 100644 --- a/public/app/plugins/datasource/loki/querybuilder/parsing.ts +++ b/public/app/plugins/datasource/loki/querybuilder/parsing.ts @@ -58,7 +58,14 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex } case 'LineFilter': { - visQuery.operations.push(getLineFilter(expr, node)); + const { operation, error } = getLineFilter(expr, node); + if (operation) { + visQuery.operations.push(operation); + } + // Show error for query patterns not supported in visual query builder + if (error) { + context.errors.push(createNotSupportedError(expr, node, error)); + } break; } @@ -68,11 +75,23 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex } case 'LabelFilter': { - visQuery.operations.push(getLabelFilter(expr, node)); + const { operation, error } = getLabelFilter(expr, node); + if (operation) { + visQuery.operations.push(operation); + } + // Show error for query patterns not supported in visual query builder + if (error) { + context.errors.push(createNotSupportedError(expr, node, error)); + } break; } - // Need to figure out JsonExpressionParser + case 'JsonExpressionParser': { + // JsonExpressionParser is not supported in query builder + const error = 'JsonExpressionParser not supported in visual query builder'; + + context.errors.push(createNotSupportedError(expr, node, error)); + } case 'LineFormatExpr': { visQuery.operations.push(getLineFormat(expr, node)); @@ -85,7 +104,15 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex } case 'UnwrapExpr': { - visQuery.operations.push(getUnwrap(expr, node)); + const { operation, error } = getUnwrap(expr, node); + if (operation) { + visQuery.operations.push(operation); + } + // Show error for query patterns not supported in visual query builder + if (error) { + context.errors.push(createNotSupportedError(expr, node, error)); + } + break; } @@ -139,7 +166,15 @@ function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter { }; } -function getLineFilter(expr: string, node: SyntaxNode): QueryBuilderOperation { +function getLineFilter(expr: string, node: SyntaxNode): { operation?: QueryBuilderOperation; error?: string } { + // Check for nodes not supported in visual builder and return error + const ipLineFilter = getAllByType(expr, node, 'Ip'); + if (ipLineFilter.length > 0) { + return { + error: 'Matching ip addresses not supported in query builder', + }; + } + const mapFilter: any = { '|=': '__line_contains', '!=': '__line_contains_not', @@ -150,8 +185,10 @@ function getLineFilter(expr: string, node: SyntaxNode): QueryBuilderOperation { const filterExpr = handleQuotes(getString(expr, node.getChild('String'))); return { - id: mapFilter[filter], - params: [filterExpr], + operation: { + id: mapFilter[filter], + params: [filterExpr], + }, }; } @@ -167,9 +204,20 @@ function getLabelParser(expr: string, node: SyntaxNode): QueryBuilderOperation { }; } -function getLabelFilter(expr: string, node: SyntaxNode): QueryBuilderOperation { - const id = '__label_filter'; +function getLabelFilter(expr: string, node: SyntaxNode): { operation?: QueryBuilderOperation; error?: string } { + // Check for nodes not supported in visual builder and return error + if (node.getChild('Or') || node.getChild('And') || node.getChild('Comma')) { + return { + error: 'Label filter with comma, "and", "or" not supported in query builder', + }; + } + if (node.firstChild!.name === 'IpLabelFilter') { + return { + error: 'IpLabelFilter not supported in query builder', + }; + } + const id = '__label_filter'; if (node.firstChild!.name === 'UnitFilter') { const filter = node.firstChild!.firstChild; const label = filter!.firstChild; @@ -178,47 +226,35 @@ function getLabelFilter(expr: string, node: SyntaxNode): QueryBuilderOperation { const valueString = handleQuotes(getString(expr, value)); return { - id, - params: [getString(expr, label), getString(expr, op), valueString], + operation: { + id, + params: [getString(expr, label), getString(expr, op), valueString], + }, }; } + // In this case it is Matcher or NumberFilter + const filter = node.firstChild; + const label = filter!.firstChild; + const op = label!.nextSibling; + const value = op!.nextSibling; + const params = [getString(expr, label), getString(expr, op), handleQuotes(getString(expr, value))]; - if (node.firstChild!.name === 'IpLabelFilter') { - // Not implemented in visual query builder yet - const filter = node.firstChild!; - const label = filter.firstChild!; - const op = label.nextSibling!; - const ip = label.nextSibling; - const value = op.nextSibling!; + // Special case of pipe filtering - no errors + if (params.join('') === `__error__=`) { return { - id, - params: [ - getString(expr, label), - getString(expr, op), - handleQuotes(getString(expr, ip)), - handleQuotes(getString(expr, value)), - ], - }; - } else { - // In this case it is Matcher or NumberFilter - const filter = node.firstChild; - const label = filter!.firstChild; - const op = label!.nextSibling; - const value = op!.nextSibling; - const params = [getString(expr, label), getString(expr, op), handleQuotes(getString(expr, value))]; - //Special case of pipe filtering - no errors - if (params.join('') === `__error__=`) { - return { + operation: { id: '__label_filter_no_errors', params: [], - }; - } - - return { - id, - params, + }, }; } + + return { + operation: { + id, + params, + }, + }; } function getLineFormat(expr: string, node: SyntaxNode): QueryBuilderOperation { @@ -241,17 +277,26 @@ function getLabelFormat(expr: string, node: SyntaxNode): QueryBuilderOperation { return { id, - params: [getString(expr, identifier), getString(expr, op), valueString], + params: [getString(expr, identifier), valueString], }; } -function getUnwrap(expr: string, node: SyntaxNode): QueryBuilderOperation { +function getUnwrap(expr: string, node: SyntaxNode): { operation?: QueryBuilderOperation; error?: string } { + // Check for nodes not supported in visual builder and return error + if (node.getChild('ConvOp')) { + return { + error: 'Unwrap with conversion operator not supported in query builder', + }; + } + const id = 'unwrap'; const string = getString(expr, node.getChild('Identifier')); return { - id, - params: [string], + operation: { + id, + params: [string], + }, }; } @@ -439,3 +484,15 @@ function getLastChildWithSelector(node: SyntaxNode, selector: string) { } return child; } + +/** + * Helper function to enrich error text with information that visual query builder doesn't support that logQL + * @param expr + * @param node + * @param error + */ +function createNotSupportedError(expr: string, node: SyntaxNode, error: string) { + const err = makeError(expr, node); + err.text = `${error}: ${err.text}`; + return err; +}