diff --git a/public/app/plugins/datasource/prometheus/querybuilder/binaryScalarOperations.ts b/public/app/plugins/datasource/prometheus/querybuilder/binaryScalarOperations.ts index 1422a561d5c..8263cb1aad3 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/binaryScalarOperations.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/binaryScalarOperations.ts @@ -1,4 +1,4 @@ -import { QueryBuilderOperation, QueryBuilderOperationDef } from './shared/types'; +import { QueryBuilderOperation, QueryBuilderOperationDef, QueryBuilderOperationParamDef } from './shared/types'; import { PromOperationId, PromVisualQueryOperationCategory } from './types'; import { defaultAddOperationHandler } from './shared/operationUtils'; @@ -37,42 +37,55 @@ export const binaryScalarDefs = [ id: PromOperationId.EqualTo, name: 'Equal to', sign: '==', + comparison: true, }, { id: PromOperationId.NotEqualTo, name: 'Not equal to', sign: '!=', + comparison: true, }, { id: PromOperationId.GreaterThan, name: 'Greater than', sign: '>', + comparison: true, }, { id: PromOperationId.LessThan, name: 'Less than', sign: '<', + comparison: true, }, { id: PromOperationId.GreaterOrEqual, name: 'Greater or equal to', sign: '>=', + comparison: true, }, { id: PromOperationId.LessOrEqual, name: 'Less or equal to', sign: '<=', + comparison: true, }, ]; // Not sure about this one. It could also be a more generic 'Simple math operation' where user specifies // both the operator and the operand in a single input -export const binaryScalarOperations = binaryScalarDefs.map((opDef) => { +export const binaryScalarOperations: QueryBuilderOperationDef[] = binaryScalarDefs.map((opDef) => { + const params: QueryBuilderOperationParamDef[] = [{ name: 'Value', type: 'number' }]; + const defaultParams: any[] = [2]; + if (opDef.comparison) { + params.unshift({ name: 'Bool', type: 'boolean' }); + defaultParams.unshift(false); + } + return { id: opDef.id, name: opDef.name, - params: [{ name: 'Value', type: 'number' }], - defaultParams: [2], + params, + defaultParams, alternativesKey: 'binary scalar operations', category: PromVisualQueryOperationCategory.BinaryOps, renderer: getSimpleBinaryRenderer(opDef.sign), @@ -82,6 +95,13 @@ export const binaryScalarOperations = binaryScalarDefs.map((opDef) => { function getSimpleBinaryRenderer(operator: string) { return function binaryRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) { - return `${innerExpr} ${operator} ${model.params[0]}`; + let param = model.params[0]; + let bool = ''; + if (model.params.length === 2) { + param = model.params[1]; + bool = model.params[0] ? ' bool' : ''; + } + + return `${innerExpr} ${operator}${bool} ${param}`; }; } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts b/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts index cc2f6aa2f62..1b86c3884a7 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts @@ -414,7 +414,7 @@ describe('buildVisualQueryFromString', () => { }); it('handles scalar comparison operators', () => { - expect(buildVisualQueryFromString('cluster_namespace_slug_dialer_name <= 2')).toEqual({ + expect(buildVisualQueryFromString('cluster_namespace_slug_dialer_name <= 2.5')).toEqual({ errors: [], query: { metric: 'cluster_namespace_slug_dialer_name', @@ -422,7 +422,23 @@ describe('buildVisualQueryFromString', () => { operations: [ { id: '__less_or_equal', - params: [2], + params: [false, 2.5], + }, + ], + }, + }); + }); + + 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: [true, 2], }, ], }, diff --git a/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts b/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts index 2787b41929e..cba3369e44c 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts @@ -324,9 +324,12 @@ function updateFunctionArgs(expr: string, node: SyntaxNode | null, context: Cont } const operatorToOpName = binaryScalarDefs.reduce((acc, def) => { - acc[def.sign] = def.id; + acc[def.sign] = { + id: def.id, + comparison: def.comparison, + }; return acc; -}, {} as Record); +}, {} as Record); /** * Right now binary expressions can be represented in 2 way in visual query. As additional operation in case it is @@ -339,10 +342,10 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) { const visQuery = context.query; const left = node.firstChild!; const op = getString(expr, left.nextSibling); - // TODO: we are skipping BinModifiers + const binModifier = node.getChild('BinModifiers')?.getChild('Bool'); const right = node.lastChild!; - const opName = operatorToOpName[op]; + const opDef = operatorToOpName[op]; const leftNumber = left.getChild('NumberLiteral'); const rightNumber = right.getChild('NumberLiteral'); @@ -359,7 +362,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) { if (rightNumber) { // TODO: this should be already handled in case parent is binary expression as it has to be added to parent // if query starts with a number that isn't handled now. - visQuery.operations.push({ id: opName, params: [parseInt(getString(expr, right), 10)] }); + visQuery.operations.push(makeBinOp(opDef, expr, right, binModifier)); } else { handleExpression(expr, right, context); } @@ -378,7 +381,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) { // 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({ id: opName, params: [parseInt(getString(expr, leftMostChild), 10)] }); + visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, binModifier)); } // If we added the first number literal as operation here we still can continue and handle the rest as the first // number will be just skipped. @@ -404,6 +407,22 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) { } } +function makeBinOp( + opDef: { id: string; comparison?: boolean }, + expr: string, + numberNode: SyntaxNode, + binModifier?: SyntaxNode | null +) { + const params: any[] = [parseFloat(getString(expr, numberNode))]; + if (opDef.comparison) { + params.unshift(Boolean(binModifier)); + } + return { + id: opDef.id, + params, + }; +} + /** * Get the actual string of the expression. That is not stored in the tree so we have to get the indexes from the node * and then based on that get it from the expression. diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx index 8e61a5abba2..55433edd8fc 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx @@ -1,5 +1,5 @@ import { SelectableValue, toOption } from '@grafana/data'; -import { Select } from '@grafana/ui'; +import { Checkbox, Select } from '@grafana/ui'; import React, { ComponentType } from 'react'; import { QueryBuilderOperationParamDef, QueryBuilderOperationParamEditorProps } from '../shared/types'; import { AutoSizeInput } from './AutoSizeInput'; @@ -16,14 +16,21 @@ export function getOperationParamEditor( return SelectInputParamEditor; } - return SimpleInputParamEditor; + switch (paramDef.type) { + case 'boolean': + return BoolInputParamEditor; + case 'number': + case 'string': + default: + return SimpleInputParamEditor; + } } function SimpleInputParamEditor(props: QueryBuilderOperationParamEditorProps) { return ( props.onChange(props.index, evt.currentTarget.checked)} + /> + ); +} + function SelectInputParamEditor({ paramDef, value, diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/operationUtils.ts b/public/app/plugins/datasource/prometheus/querybuilder/shared/operationUtils.ts index 37bb131f256..d49c2954be7 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/operationUtils.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/operationUtils.ts @@ -5,6 +5,7 @@ import { QueryBuilderOperationParamDef, QueryWithOperations, } from './types'; +import { SelectableValue } from '@grafana/data/src'; export function functionRendererLeft(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) { const params = renderParams(model, def, innerExpr); @@ -116,7 +117,7 @@ export function getOperationParamId(operationIndex: number, paramIndex: number) } export function getRangeVectorParamDef(withRateInterval = false): QueryBuilderOperationParamDef { - const param = { + const param: QueryBuilderOperationParamDef = { name: 'Range', type: 'string', options: [ @@ -134,7 +135,7 @@ export function getRangeVectorParamDef(withRateInterval = false): QueryBuilderOp }; if (withRateInterval) { - param.options.unshift({ + (param.options as Array>).unshift({ label: '$__rate_interval', value: '$__rate_interval', // tooltip: 'Always above 4x scrape interval', diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts b/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts index 30827c3309f..f399702c285 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts @@ -52,11 +52,11 @@ export type QueryBuilderOperationRenderer = ( innerExpr: string ) => string; -export type QueryBuilderOperationParamValue = string | number; +export type QueryBuilderOperationParamValue = string | number | boolean; export interface QueryBuilderOperationParamDef { name: string; - type: string; + type: 'string' | 'number' | 'boolean'; options?: string[] | number[] | Array>; hideName?: boolean; restParam?: boolean;