The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/datasource/tempo/traceql/highlighting.test.ts

195 lines
8.0 KiB

import { monacoTypes } from '@grafana/ui';
import { computeErrorMessage, getErrorNodes, getWarningMarkers } from './highlighting';
describe('Highlighting', () => {
describe('gets correct warning markers for', () => {
const message = 'Add resource or span scope to attribute to improve query performance.';
describe('no warnings', () => {
it('for span scope', () => {
const { model } = setup('{ span.component = "http" }');
const marker = getWarningMarkers(4, model);
expect(marker).toEqual(expect.objectContaining([]));
});
it('for resource scope', () => {
const { model } = setup('{ resource.component = "http" }');
const marker = getWarningMarkers(4, model);
expect(marker).toEqual(expect.objectContaining([]));
});
it('for parent scope', () => {
const { model } = setup('{ parent.component = "http" }');
const marker = getWarningMarkers(4, model);
expect(marker).toEqual(expect.objectContaining([]));
});
});
it('single warning', () => {
const { model } = setup('{ .component = "http" }');
const marker = getWarningMarkers(4, model);
expect(marker).toEqual(
expect.objectContaining([
{
message,
severity: 4,
startLineNumber: 1,
endLineNumber: 1,
startColumn: 3,
endColumn: 3,
},
])
);
});
it('multiple warnings', () => {
const { model } = setup('{ .component = "http" || .http.status_code = 200 }');
const marker = getWarningMarkers(4, model);
expect(marker).toEqual(
expect.objectContaining([
{
message,
severity: 4,
startLineNumber: 1,
endLineNumber: 1,
startColumn: 3,
endColumn: 3,
},
{
message,
severity: 4,
startLineNumber: 1,
endLineNumber: 1,
startColumn: 26,
endColumn: 26,
},
])
);
});
it('multiple parts, single warning', () => {
const { model } = setup('{ resource.component = "http" || .http.status_code = 200 }');
const marker = getWarningMarkers(4, model);
expect(marker).toEqual(
expect.objectContaining([
{
message,
severity: 4,
startLineNumber: 1,
endLineNumber: 1,
startColumn: 34,
endColumn: 34,
},
])
);
});
});
describe('check for syntax errors in query', () => {
it.each([
['{span.http.status_code = }', 'Invalid value after comparison or arithmetic operator.'],
['{span.http.status_code 200}', 'Invalid comparison operator after field expression.'],
['{span.http.status_code ""}', 'Invalid operator after field expression.'],
['{span.http.status_code @ 200}', 'Invalid comparison operator after field expression.'],
['{span.http.status_code span.http.status_code}', 'Invalid operator after field expression.'],
[
'{span.http.status_code = 200} {span.http.status_code = 200}',
'Invalid spanset combining operator after spanset expression.',
],
[
'{span.http.status_code = 200} + {span.http.status_code = 200}',
'Invalid spanset combining operator after spanset expression.',
],
['{span.http.status_code = 200} &&', 'Invalid spanset expression after spanset combining operator.'],
[
'{span.http.status_code = 200} && {span.http.status_code = 200} | foo() > 3',
'Invalid aggregation operator after pipeline operator.',
],
[
'{span.http.status_code = 200} && {span.http.status_code = 200} | avg() > 3',
'Invalid expression for aggregator operator.',
],
['{ 1 + 1 = 2 + }', 'Invalid value after comparison or arithmetic operator.'],
['{ .a && }', 'Invalid value after logical operator.'],
['{ .a || }', 'Invalid value after logical operator.'],
['{ .a + }', 'Invalid value after comparison or arithmetic operator.'],
['{ 200 = 200 200 }', 'Invalid comparison operator after field expression.'],
['{.foo 300}', 'Invalid comparison operator after field expression.'],
['{.foo 300 && .bar = 200}', 'Invalid operator after field expression.'],
['{.foo 300 && .bar 200}', 'Invalid operator after field expression.'],
['{.foo=1} {.bar=2}', 'Invalid spanset combining operator after spanset expression.'],
['{ span.http.status_code = 200 && }', 'Invalid value after logical operator.'],
['{ span.http.status_code = 200 || }', 'Invalid value after logical operator.'],
['{ .foo = 200 } && ', 'Invalid spanset expression after spanset combining operator.'],
['{ .foo = 200 } || ', 'Invalid spanset expression after spanset combining operator.'],
['{ .foo = 200 } >> ', 'Invalid spanset expression after spanset combining operator.'],
['{.foo=1} | avg()', 'Invalid expression for aggregator operator.'],
['{.foo=1} | avg(.foo) > ', 'Invalid value after comparison operator.'],
['{.foo=1} | avg() < 1s', 'Invalid expression for aggregator operator.'],
['{.foo=1} | max() = 3', 'Invalid expression for aggregator operator.'],
['{.foo=1} | by()', 'Invalid expression for by operator.'],
['{.foo=1} | select()', 'Invalid expression for select operator.'],
['{foo}', 'Invalid expression for spanset.'],
['{.}', 'Invalid expression for spanset.'],
['{ resource. }', 'Invalid expression for spanset.'],
['{ span. }', 'Invalid expression for spanset.'],
['{.foo=}', 'Invalid value after comparison or arithmetic operator.'],
['{.foo="}', 'Invalid value after comparison or arithmetic operator.'],
['{.foo=300} |', 'Invalid aggregation operator after pipeline operator.'],
['{.foo=300} && {.bar=200} |', 'Invalid aggregation operator after pipeline operator.'],
['{.foo=300} && {.bar=300} && {.foo=300} |', 'Invalid aggregation operator after pipeline operator.'],
['{.foo=300} | avg(.value)', 'Invalid comparison operator after aggregator operator.'],
['{.foo=300} && {.foo=300} | avg(.value)', 'Invalid comparison operator after aggregator operator.'],
['{.foo=300} | avg(.value) =', 'Invalid value after comparison operator.'],
['{.foo=300} && {.foo=300} | avg(.value) =', 'Invalid value after comparison operator.'],
['{.foo=300} | max(duration) > 1hs', 'Invalid value after comparison operator.'],
['{ span.http.status_code', 'Invalid comparison operator after field expression.'],
['{ .foo = "bar"', 'Invalid comparison operator after field expression.'],
['abcxyz', 'Invalid query.'],
])('error message for invalid query - %s, %s', (query: string, expectedErrorMessage: string) => {
const errorNode = getErrorNodes(query)[0];
expect(computeErrorMessage(errorNode)).toBe(expectedErrorMessage);
});
it.each([
['123'],
['abc'],
['1a2b3c'],
['{span.status = $code}'],
['{span.${attribute} = "GET"}'],
['{span.${attribute:format} = ${value:format} }'],
['{true} >> {true}'],
['{true} << {true}'],
['{true} !>> {true}'],
['{true} !<< {true}'],
[
`{ true } /* && { false } && */ && { true } // && { false }
&& { true }`,
],
['{span.s"t\\"at"us}'],
['{span.s"t\\\\at"us}'],
['{ span.s"tat"us" = "GET123 }'], // weird query, but technically valid
['{ duration = 123.456us}'],
['{ .foo = `GET` && .bar = `P\'O"S\\T` }'],
['{ .foo = `GET` } | by(.foo, name)'],
])('valid query - %s', (query: string) => {
expect(getErrorNodes(query)).toStrictEqual([]);
});
});
});
function setup(value: string) {
const model = makeModel(value);
return { model } as unknown as { model: monacoTypes.editor.ITextModel };
}
function makeModel(value: string) {
return {
id: 'test_monaco',
getValue() {
return value;
},
getLineLength() {
return value.length;
},
};
}