mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Parsing for vector matching and UI changes (#46668)
This commit is contained in:
@@ -258,26 +258,6 @@ describe('PromQueryModeller', () => {
|
||||
).toBe('(metric_a * 1000) / metric_b');
|
||||
});
|
||||
|
||||
it('Can render with binary queries with vectorMatches expression', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
metric: 'metric_a',
|
||||
labels: [],
|
||||
operations: [],
|
||||
binaryQueries: [
|
||||
{
|
||||
operator: '/',
|
||||
vectorMatches: 'on(le)',
|
||||
query: {
|
||||
metric: 'metric_b',
|
||||
labels: [],
|
||||
operations: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBe('metric_a / on(le) metric_b');
|
||||
});
|
||||
it('Can render functions that require a range as a parameter', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
@@ -305,6 +285,7 @@ describe('PromQueryModeller', () => {
|
||||
})
|
||||
).toBe('label_join(metric_a, "label_1", ",", "label_2")');
|
||||
});
|
||||
|
||||
it('Can render label_join with extra parameters', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
@@ -314,4 +295,26 @@ describe('PromQueryModeller', () => {
|
||||
})
|
||||
).toBe('label_join(metric_a, "label_1", ", ", "label_2", "label_3", "label_4", "label_5")');
|
||||
});
|
||||
|
||||
it('can render vector matchers', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
metric: 'metric_a',
|
||||
labels: [],
|
||||
operations: [],
|
||||
binaryQueries: [
|
||||
{
|
||||
operator: '/',
|
||||
vectorMatches: 'le, foo',
|
||||
vectorMatchesType: 'on',
|
||||
query: {
|
||||
metric: 'metric_b',
|
||||
labels: [],
|
||||
operations: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBe('metric_a / on(le, foo) metric_b');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,18 +37,35 @@ export const NestedQuery = React.memo<Props>(({ nestedQuery, index, datasource,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.name}>Vector matches</div>
|
||||
|
||||
<AutoSizeInput
|
||||
minWidth={20}
|
||||
defaultValue={nestedQuery.vectorMatches}
|
||||
onCommitChange={(evt) => {
|
||||
onChange(index, {
|
||||
...nestedQuery,
|
||||
vectorMatches: evt.currentTarget.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={styles.vectorMatchWrapper}>
|
||||
<Select<PromVisualQueryBinary['vectorMatchesType']>
|
||||
width="auto"
|
||||
value={nestedQuery.vectorMatchesType || 'on'}
|
||||
allowCustomValue
|
||||
options={[
|
||||
{ value: 'on', label: 'on' },
|
||||
{ value: 'ignoring', label: 'ignoring' },
|
||||
]}
|
||||
onChange={(val) => {
|
||||
onChange(index, {
|
||||
...nestedQuery,
|
||||
vectorMatchesType: val.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<AutoSizeInput
|
||||
className={styles.vectorMatchInput}
|
||||
minWidth={20}
|
||||
defaultValue={nestedQuery.vectorMatches}
|
||||
onCommitChange={(evt) => {
|
||||
onChange(index, {
|
||||
...nestedQuery,
|
||||
vectorMatches: evt.currentTarget.value,
|
||||
vectorMatchesType: nestedQuery.vectorMatchesType || 'on',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<FlexItem grow={1} />
|
||||
<IconButton name="times" size="sm" onClick={() => onRemove(index)} />
|
||||
</div>
|
||||
@@ -76,21 +93,33 @@ NestedQuery.displayName = 'NestedQuery';
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
card: css({
|
||||
label: 'card',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(0.5),
|
||||
}),
|
||||
header: css({
|
||||
label: 'header',
|
||||
padding: theme.spacing(0.5, 0.5, 0.5, 1),
|
||||
gap: theme.spacing(1),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
name: css({
|
||||
label: 'name',
|
||||
whiteSpace: 'nowrap',
|
||||
}),
|
||||
body: css({
|
||||
label: 'body',
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
vectorMatchInput: css({
|
||||
label: 'vectorMatchInput',
|
||||
marginLeft: -1,
|
||||
}),
|
||||
vectorMatchWrapper: css({
|
||||
label: 'vectorMatchWrapper',
|
||||
display: 'flex',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -505,6 +505,42 @@ describe('buildVisualQueryFromString', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
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: [] },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function noErrors(query: PromVisualQuery) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { parser } from 'lezer-promql';
|
||||
import { SyntaxNode, TreeCursor } from '@lezer/common';
|
||||
import { QueryBuilderLabelFilter, QueryBuilderOperation } from './shared/types';
|
||||
import { PromVisualQuery } from './types';
|
||||
import { PromVisualQuery, PromVisualQueryBinary } from './types';
|
||||
import { binaryScalarDefs } from './binaryScalarOperations';
|
||||
|
||||
// Taken from template_srv, but copied so to not mess with the regex.index which is manipulated in the service
|
||||
@@ -342,7 +342,8 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
const visQuery = context.query;
|
||||
const left = node.firstChild!;
|
||||
const op = getString(expr, left.nextSibling);
|
||||
const binModifier = node.getChild('BinModifiers')?.getChild('Bool');
|
||||
const binModifier = getBinaryModifier(expr, node.getChild('BinModifiers'));
|
||||
|
||||
const right = node.lastChild!;
|
||||
|
||||
const opDef = operatorToOpName[op];
|
||||
@@ -362,13 +363,13 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
}
|
||||
|
||||
if (rightNumber) {
|
||||
visQuery.operations.push(makeBinOp(opDef, expr, right, binModifier));
|
||||
visQuery.operations.push(makeBinOp(opDef, expr, right, !!binModifier?.isBool));
|
||||
} else if (rightBinary) {
|
||||
// Due to the way binary ops are parsed we can get a binary operation on the right that starts with a number which
|
||||
// is a factor for a current binary operation. So we have to add it as an operation now.
|
||||
const leftMostChild = getLeftMostChild(right);
|
||||
if (leftMostChild?.name === 'NumberLiteral') {
|
||||
visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, binModifier));
|
||||
visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, !!binModifier?.isBool));
|
||||
}
|
||||
|
||||
// If we added the first number literal as operation here we still can continue and handle the rest as the first
|
||||
@@ -376,7 +377,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
handleExpression(expr, right, context);
|
||||
} else {
|
||||
visQuery.binaryQueries = visQuery.binaryQueries || [];
|
||||
const binQuery = {
|
||||
const binQuery: PromVisualQueryBinary = {
|
||||
operator: op,
|
||||
query: {
|
||||
metric: '',
|
||||
@@ -384,6 +385,10 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
operations: [],
|
||||
},
|
||||
};
|
||||
if (binModifier?.isMatcher) {
|
||||
binQuery.vectorMatchesType = binModifier.matchType;
|
||||
binQuery.vectorMatches = binModifier.matches;
|
||||
}
|
||||
visQuery.binaryQueries.push(binQuery);
|
||||
handleExpression(expr, right, {
|
||||
query: binQuery.query,
|
||||
@@ -392,15 +397,43 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
function getBinaryModifier(
|
||||
expr: string,
|
||||
node: SyntaxNode | null
|
||||
):
|
||||
| { isBool: true; isMatcher: false }
|
||||
| { isBool: false; isMatcher: true; matches: string; matchType: 'ignoring' | 'on' }
|
||||
| undefined {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
if (node.getChild('Bool')) {
|
||||
return { isBool: true, isMatcher: false };
|
||||
} else {
|
||||
const matcher = node.getChild('OnOrIgnoring');
|
||||
if (!matcher) {
|
||||
// Not sure what this could be, maybe should be an error.
|
||||
return undefined;
|
||||
}
|
||||
const labels = getString(expr, matcher.getChild('GroupingLabels')?.getChild('GroupingLabelList'));
|
||||
return {
|
||||
isMatcher: true,
|
||||
isBool: false,
|
||||
matches: labels,
|
||||
matchType: matcher.getChild('On') ? 'on' : 'ignoring',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function makeBinOp(
|
||||
opDef: { id: string; comparison?: boolean },
|
||||
expr: string,
|
||||
numberNode: SyntaxNode,
|
||||
binModifier?: SyntaxNode | null
|
||||
hasBool: boolean
|
||||
) {
|
||||
const params: any[] = [parseFloat(getString(expr, numberNode))];
|
||||
if (opDef.comparison) {
|
||||
params.unshift(Boolean(binModifier));
|
||||
params.unshift(hasBool);
|
||||
}
|
||||
return {
|
||||
id: opDef.id,
|
||||
@@ -414,7 +447,7 @@ function makeBinOp(
|
||||
* @param expr
|
||||
* @param node
|
||||
*/
|
||||
function getString(expr: string, node: SyntaxNode | TreeCursor | null) {
|
||||
function getString(expr: string, node: SyntaxNode | TreeCursor | null | undefined) {
|
||||
if (!node) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
|
||||
export interface VisualQueryBinary<T> {
|
||||
operator: string;
|
||||
vectorMatchesType?: 'on' | 'ignoring';
|
||||
vectorMatches?: string;
|
||||
query: T;
|
||||
}
|
||||
@@ -66,7 +67,7 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
|
||||
let result = leftOperand + ` ${binaryQuery.operator} `;
|
||||
|
||||
if (binaryQuery.vectorMatches) {
|
||||
result += `${binaryQuery.vectorMatches} `;
|
||||
result += `${binaryQuery.vectorMatchesType}(${binaryQuery.vectorMatches}) `;
|
||||
}
|
||||
|
||||
return result + this.renderQuery(binaryQuery.query, true);
|
||||
|
||||
Reference in New Issue
Block a user