mirror of
https://github.com/grafana/grafana.git
synced 2025-09-25 18:03:50 +08:00
590 lines
14 KiB
TypeScript
590 lines
14 KiB
TypeScript
import { buildVisualQueryFromString } from './parsing';
|
|
import { PromVisualQuery } from './types';
|
|
|
|
describe('buildVisualQueryFromString', () => {
|
|
it('creates no errors for empty query', () => {
|
|
expect(buildVisualQueryFromString('')).toEqual(
|
|
noErrors({
|
|
labels: [],
|
|
operations: [],
|
|
metric: '',
|
|
})
|
|
);
|
|
});
|
|
it('parses simple query', () => {
|
|
expect(buildVisualQueryFromString('counters_logins{app="frontend"}')).toEqual(
|
|
noErrors({
|
|
metric: 'counters_logins',
|
|
labels: [
|
|
{
|
|
op: '=',
|
|
value: 'frontend',
|
|
label: 'app',
|
|
},
|
|
],
|
|
operations: [],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses query with rate and interval', () => {
|
|
expect(buildVisualQueryFromString('rate(counters_logins{app="frontend"}[5m])')).toEqual(
|
|
noErrors({
|
|
metric: 'counters_logins',
|
|
labels: [
|
|
{
|
|
op: '=',
|
|
value: 'frontend',
|
|
label: 'app',
|
|
},
|
|
],
|
|
operations: [
|
|
{
|
|
id: 'rate',
|
|
params: ['5m'],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses query with nested query and interval variable', () => {
|
|
expect(
|
|
buildVisualQueryFromString(
|
|
'avg(rate(access_evaluation_duration_count{instance="host.docker.internal:3000"}[$__rate_interval]))'
|
|
)
|
|
).toEqual(
|
|
noErrors({
|
|
metric: 'access_evaluation_duration_count',
|
|
labels: [
|
|
{
|
|
op: '=',
|
|
value: 'host.docker.internal:3000',
|
|
label: 'instance',
|
|
},
|
|
],
|
|
operations: [
|
|
{
|
|
id: 'rate',
|
|
params: ['$__rate_interval'],
|
|
},
|
|
{
|
|
id: 'avg',
|
|
params: [],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses query with aggregation by labels', () => {
|
|
const visQuery = {
|
|
metric: 'metric_name',
|
|
labels: [
|
|
{
|
|
label: 'instance',
|
|
op: '=',
|
|
value: 'internal:3000',
|
|
},
|
|
],
|
|
operations: [
|
|
{
|
|
id: '__sum_by',
|
|
params: ['app', 'version'],
|
|
},
|
|
],
|
|
};
|
|
expect(buildVisualQueryFromString('sum(metric_name{instance="internal:3000"}) by (app, version)')).toEqual(
|
|
noErrors(visQuery)
|
|
);
|
|
expect(buildVisualQueryFromString('sum by (app, version)(metric_name{instance="internal:3000"})')).toEqual(
|
|
noErrors(visQuery)
|
|
);
|
|
});
|
|
|
|
it('parses query with aggregation without labels', () => {
|
|
const visQuery = {
|
|
metric: 'metric_name',
|
|
labels: [
|
|
{
|
|
label: 'instance',
|
|
op: '=',
|
|
value: 'internal:3000',
|
|
},
|
|
],
|
|
operations: [
|
|
{
|
|
id: '__sum_without',
|
|
params: ['app', 'version'],
|
|
},
|
|
],
|
|
};
|
|
expect(buildVisualQueryFromString('sum(metric_name{instance="internal:3000"}) without (app, version)')).toEqual(
|
|
noErrors(visQuery)
|
|
);
|
|
expect(buildVisualQueryFromString('sum without (app, version)(metric_name{instance="internal:3000"})')).toEqual(
|
|
noErrors(visQuery)
|
|
);
|
|
});
|
|
|
|
it('parses aggregation with params', () => {
|
|
expect(buildVisualQueryFromString('topk(5, http_requests_total)')).toEqual(
|
|
noErrors({
|
|
metric: 'http_requests_total',
|
|
labels: [],
|
|
operations: [
|
|
{
|
|
id: 'topk',
|
|
params: [5],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses aggregation with params and labels', () => {
|
|
expect(buildVisualQueryFromString('topk by(instance, job) (5, http_requests_total)')).toEqual(
|
|
noErrors({
|
|
metric: 'http_requests_total',
|
|
labels: [],
|
|
operations: [
|
|
{
|
|
id: '__topk_by',
|
|
params: [5, 'instance', 'job'],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses function with argument', () => {
|
|
expect(
|
|
buildVisualQueryFromString('histogram_quantile(0.99, rate(counters_logins{app="backend"}[$__rate_interval]))')
|
|
).toEqual(
|
|
noErrors({
|
|
metric: 'counters_logins',
|
|
labels: [{ label: 'app', op: '=', value: 'backend' }],
|
|
operations: [
|
|
{
|
|
id: 'rate',
|
|
params: ['$__rate_interval'],
|
|
},
|
|
{
|
|
id: 'histogram_quantile',
|
|
params: [0.99],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses function with multiple arguments', () => {
|
|
expect(
|
|
buildVisualQueryFromString(
|
|
'label_replace(avg_over_time(http_requests_total{instance="foo"}[$__interval]), "instance", "$1", "", "(.*)")'
|
|
)
|
|
).toEqual(
|
|
noErrors({
|
|
metric: 'http_requests_total',
|
|
labels: [{ label: 'instance', op: '=', value: 'foo' }],
|
|
operations: [
|
|
{
|
|
id: 'avg_over_time',
|
|
params: ['$__interval'],
|
|
},
|
|
{
|
|
id: 'label_replace',
|
|
params: ['instance', '$1', '', '(.*)'],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses binary operation with scalar', () => {
|
|
expect(buildVisualQueryFromString('avg_over_time(http_requests_total{instance="foo"}[$__interval]) / 2')).toEqual(
|
|
noErrors({
|
|
metric: 'http_requests_total',
|
|
labels: [{ label: 'instance', op: '=', value: 'foo' }],
|
|
operations: [
|
|
{
|
|
id: 'avg_over_time',
|
|
params: ['$__interval'],
|
|
},
|
|
{
|
|
id: '__divide_by',
|
|
params: [2],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses binary operation with 2 queries', () => {
|
|
expect(
|
|
buildVisualQueryFromString('avg_over_time(http_requests_total{instance="foo"}[$__interval]) / sum(logins_count)')
|
|
).toEqual(
|
|
noErrors({
|
|
metric: 'http_requests_total',
|
|
labels: [{ label: 'instance', op: '=', value: 'foo' }],
|
|
operations: [{ id: 'avg_over_time', params: ['$__interval'] }],
|
|
binaryQueries: [
|
|
{
|
|
operator: '/',
|
|
query: {
|
|
metric: 'logins_count',
|
|
labels: [],
|
|
operations: [{ id: 'sum', params: [] }],
|
|
},
|
|
},
|
|
],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses template variables in strings', () => {
|
|
expect(buildVisualQueryFromString('http_requests_total{instance="$label_variable"}')).toEqual(
|
|
noErrors({
|
|
metric: 'http_requests_total',
|
|
labels: [{ label: 'instance', op: '=', value: '$label_variable' }],
|
|
operations: [],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses template variables for metric', () => {
|
|
expect(buildVisualQueryFromString('$metric_variable{instance="foo"}')).toEqual(
|
|
noErrors({
|
|
metric: '$metric_variable',
|
|
labels: [{ label: 'instance', op: '=', value: 'foo' }],
|
|
operations: [],
|
|
})
|
|
);
|
|
|
|
expect(buildVisualQueryFromString('${metric_variable:fmt}{instance="foo"}')).toEqual(
|
|
noErrors({
|
|
metric: '${metric_variable:fmt}',
|
|
labels: [{ label: 'instance', op: '=', value: 'foo' }],
|
|
operations: [],
|
|
})
|
|
);
|
|
|
|
expect(buildVisualQueryFromString('[[metric_variable:fmt]]{instance="foo"}')).toEqual(
|
|
noErrors({
|
|
metric: '[[metric_variable:fmt]]',
|
|
labels: [{ label: 'instance', op: '=', value: 'foo' }],
|
|
operations: [],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('parses template variables in label name', () => {
|
|
expect(buildVisualQueryFromString('metric{${variable_label}="foo"}')).toEqual(
|
|
noErrors({
|
|
metric: 'metric',
|
|
labels: [{ label: '${variable_label}', op: '=', value: 'foo' }],
|
|
operations: [],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('fails to parse variable for function', () => {
|
|
expect(buildVisualQueryFromString('${func_var}(metric{bar="foo"})')).toEqual({
|
|
errors: [
|
|
{
|
|
text: '(',
|
|
from: 20,
|
|
to: 21,
|
|
parentType: 'VectorSelector',
|
|
},
|
|
{
|
|
text: 'metric',
|
|
from: 21,
|
|
to: 27,
|
|
parentType: 'VectorSelector',
|
|
},
|
|
],
|
|
query: {
|
|
metric: '${func_var}',
|
|
labels: [{ label: 'bar', op: '=', value: 'foo' }],
|
|
operations: [],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('fails to parse malformed query', () => {
|
|
expect(buildVisualQueryFromString('asdf-metric{bar="})')).toEqual({
|
|
errors: [
|
|
{
|
|
text: '',
|
|
from: 19,
|
|
to: 19,
|
|
parentType: 'LabelMatchers',
|
|
},
|
|
],
|
|
query: {
|
|
metric: 'asdf',
|
|
labels: [],
|
|
operations: [],
|
|
binaryQueries: [
|
|
{
|
|
operator: '-',
|
|
query: {
|
|
metric: 'metric',
|
|
labels: [{ label: 'bar', op: '=', value: '})' }],
|
|
operations: [],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('fails to parse malformed query 2', () => {
|
|
expect(buildVisualQueryFromString('ewafweaf{afea=afe}')).toEqual({
|
|
errors: [
|
|
{
|
|
text: 'afe}',
|
|
from: 14,
|
|
to: 18,
|
|
parentType: 'LabelMatcher',
|
|
},
|
|
],
|
|
query: {
|
|
metric: 'ewafweaf',
|
|
labels: [{ label: 'afea', op: '=', value: '' }],
|
|
operations: [],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('parses query without metric', () => {
|
|
expect(buildVisualQueryFromString('label_replace(rate([$__rate_interval]), "", "$1", "", "(.*)")')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: '',
|
|
labels: [],
|
|
operations: [
|
|
{ id: 'rate', params: ['$__rate_interval'] },
|
|
{
|
|
id: 'label_replace',
|
|
params: ['', '$1', '', '(.*)'],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('lone aggregation without params', () => {
|
|
expect(buildVisualQueryFromString('sum()')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: '',
|
|
labels: [],
|
|
operations: [{ id: 'sum', params: [] }],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('handles multiple binary scalar operations', () => {
|
|
expect(buildVisualQueryFromString('cluster_namespace_slug_dialer_name + 1 - 1 / 1 * 1 % 1 ^ 1')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: 'cluster_namespace_slug_dialer_name',
|
|
labels: [],
|
|
operations: [
|
|
{
|
|
id: '__addition',
|
|
params: [1],
|
|
},
|
|
{
|
|
id: '__subtraction',
|
|
params: [1],
|
|
},
|
|
{
|
|
id: '__divide_by',
|
|
params: [1],
|
|
},
|
|
{
|
|
id: '__multiply_by',
|
|
params: [1],
|
|
},
|
|
{
|
|
id: '__modulo',
|
|
params: [1],
|
|
},
|
|
{
|
|
id: '__exponent',
|
|
params: [1],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('handles scalar comparison operators', () => {
|
|
expect(buildVisualQueryFromString('cluster_namespace_slug_dialer_name <= 2.5')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: 'cluster_namespace_slug_dialer_name',
|
|
labels: [],
|
|
operations: [
|
|
{
|
|
id: '__less_or_equal',
|
|
params: [2.5, false],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('handles bool with comparison operator', () => {
|
|
expect(buildVisualQueryFromString('cluster_namespace_slug_dialer_name <= bool 2')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: 'cluster_namespace_slug_dialer_name',
|
|
labels: [],
|
|
operations: [
|
|
{
|
|
id: '__less_or_equal',
|
|
params: [2, true],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('handles multiple binary operations', () => {
|
|
expect(buildVisualQueryFromString('foo{x="yy"} * metric{y="zz",a="bb"} * metric2')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: 'foo',
|
|
labels: [{ label: 'x', op: '=', value: 'yy' }],
|
|
operations: [],
|
|
binaryQueries: [
|
|
{
|
|
operator: '*',
|
|
query: {
|
|
metric: 'metric',
|
|
labels: [
|
|
{ label: 'y', op: '=', value: 'zz' },
|
|
{ label: 'a', op: '=', value: 'bb' },
|
|
],
|
|
operations: [],
|
|
},
|
|
},
|
|
{
|
|
operator: '*',
|
|
query: {
|
|
metric: 'metric2',
|
|
labels: [],
|
|
operations: [],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('handles multiple binary operations and scalar', () => {
|
|
expect(buildVisualQueryFromString('foo{x="yy"} * metric{y="zz",a="bb"} * 2')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: 'foo',
|
|
labels: [{ label: 'x', op: '=', value: 'yy' }],
|
|
operations: [
|
|
{
|
|
id: '__multiply_by',
|
|
params: [2],
|
|
},
|
|
],
|
|
binaryQueries: [
|
|
{
|
|
operator: '*',
|
|
query: {
|
|
metric: 'metric',
|
|
labels: [
|
|
{ label: 'y', op: '=', value: 'zz' },
|
|
{ label: 'a', op: '=', value: 'bb' },
|
|
],
|
|
operations: [],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('handles binary operation with vector matchers', () => {
|
|
expect(buildVisualQueryFromString('foo * on(foo, bar) metric')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: 'foo',
|
|
labels: [],
|
|
operations: [],
|
|
binaryQueries: [
|
|
{
|
|
operator: '*',
|
|
vectorMatches: 'foo, bar',
|
|
vectorMatchesType: 'on',
|
|
query: { metric: 'metric', labels: [], operations: [] },
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(buildVisualQueryFromString('foo * ignoring(foo) metric')).toEqual({
|
|
errors: [],
|
|
query: {
|
|
metric: 'foo',
|
|
labels: [],
|
|
operations: [],
|
|
binaryQueries: [
|
|
{
|
|
operator: '*',
|
|
vectorMatches: 'foo',
|
|
vectorMatchesType: 'ignoring',
|
|
query: { metric: 'metric', labels: [], operations: [] },
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('reports error on parenthesis', () => {
|
|
expect(buildVisualQueryFromString('foo / (bar + baz)')).toEqual({
|
|
errors: [
|
|
{
|
|
from: 6,
|
|
parentType: 'Expr',
|
|
text: '(bar + baz)',
|
|
to: 17,
|
|
},
|
|
],
|
|
query: {
|
|
metric: 'foo',
|
|
labels: [],
|
|
operations: [],
|
|
binaryQueries: [
|
|
{
|
|
operator: '/',
|
|
query: {
|
|
binaryQueries: [{ operator: '+', query: { labels: [], metric: 'baz', operations: [] } }],
|
|
metric: 'bar',
|
|
labels: [],
|
|
operations: [],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
function noErrors(query: PromVisualQuery) {
|
|
return {
|
|
errors: [],
|
|
query,
|
|
};
|
|
}
|