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/prometheus/querybuilder/operations.ts

401 lines
13 KiB

import { LabelParamEditor } from './components/LabelParamEditor';
import {
defaultAddOperationHandler,
functionRendererLeft,
functionRendererRight,
getPromAndLokiOperationDisplayName,
getRangeVectorParamDef,
rangeRendererLeftWithParams,
rangeRendererRightWithParams,
} from './shared/operationUtils';
import {
QueryBuilderOperation,
QueryBuilderOperationDef,
QueryWithOperations,
VisualQueryModeller,
} from './shared/types';
import { PromOperationId, PromVisualQuery, PromVisualQueryOperationCategory } from './types';
export function getOperationDefinitions(): QueryBuilderOperationDef[] {
const list: QueryBuilderOperationDef[] = [
{
id: PromOperationId.HistogramQuantile,
name: 'Histogram quantile',
params: [{ name: 'Quantile', type: 'number', options: [0.99, 0.95, 0.9, 0.75, 0.5, 0.25] }],
defaultParams: [0.9],
category: PromVisualQueryOperationCategory.Functions,
renderer: functionRendererLeft,
addOperationHandler: defaultAddOperationHandler,
},
{
id: PromOperationId.LabelReplace,
name: 'Label replace',
params: [
{ name: 'Destination label', type: 'string' },
{ name: 'Replacement', type: 'string' },
{ name: 'Source label', type: 'string' },
{ name: 'Regex', type: 'string' },
],
category: PromVisualQueryOperationCategory.Functions,
defaultParams: ['', '$1', '', '(.*)'],
renderer: functionRendererRight,
addOperationHandler: defaultAddOperationHandler,
},
{
id: PromOperationId.Ln,
name: 'Ln',
params: [],
defaultParams: [],
category: PromVisualQueryOperationCategory.Functions,
renderer: functionRendererLeft,
addOperationHandler: defaultAddOperationHandler,
},
createRangeFunction(PromOperationId.Changes),
createRangeFunction(PromOperationId.Rate, true),
createRangeFunction(PromOperationId.Irate),
createRangeFunction(PromOperationId.Increase, true),
createRangeFunction(PromOperationId.Idelta),
createRangeFunction(PromOperationId.Delta),
createFunction({
id: PromOperationId.HoltWinters,
params: [
getRangeVectorParamDef(),
{ name: 'Smoothing Factor', type: 'number' },
{ name: 'Trend Factor', type: 'number' },
],
defaultParams: ['$__interval', 0.5, 0.5],
alternativesKey: 'range function',
category: PromVisualQueryOperationCategory.RangeFunctions,
renderer: rangeRendererRightWithParams,
addOperationHandler: addOperationWithRangeVector,
changeTypeHandler: operationTypeChangedHandlerForRangeFunction,
}),
createFunction({
id: PromOperationId.PredictLinear,
params: [getRangeVectorParamDef(), { name: 'Seconds from now', type: 'number' }],
defaultParams: ['$__interval', 60],
alternativesKey: 'range function',
category: PromVisualQueryOperationCategory.RangeFunctions,
renderer: rangeRendererRightWithParams,
addOperationHandler: addOperationWithRangeVector,
changeTypeHandler: operationTypeChangedHandlerForRangeFunction,
}),
createFunction({
id: PromOperationId.QuantileOverTime,
params: [getRangeVectorParamDef(), { name: 'Quantile', type: 'number' }],
defaultParams: ['$__interval', 0.5],
alternativesKey: 'overtime function',
category: PromVisualQueryOperationCategory.RangeFunctions,
renderer: rangeRendererLeftWithParams,
addOperationHandler: addOperationWithRangeVector,
changeTypeHandler: operationTypeChangedHandlerForRangeFunction,
}),
// 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
{
id: PromOperationId.MultiplyBy,
name: 'Multiply by scalar',
params: [{ name: 'Factor', type: 'number' }],
defaultParams: [2],
category: PromVisualQueryOperationCategory.BinaryOps,
renderer: getSimpleBinaryRenderer('*'),
addOperationHandler: defaultAddOperationHandler,
},
{
id: PromOperationId.DivideBy,
name: 'Divide by scalar',
params: [{ name: 'Factor', type: 'number' }],
defaultParams: [2],
category: PromVisualQueryOperationCategory.BinaryOps,
renderer: getSimpleBinaryRenderer('/'),
addOperationHandler: defaultAddOperationHandler,
},
{
id: PromOperationId.NestedQuery,
name: 'Binary operation with query',
params: [],
defaultParams: [],
category: PromVisualQueryOperationCategory.BinaryOps,
renderer: (model, def, innerExpr) => innerExpr,
addOperationHandler: addNestedQueryHandler,
},
createFunction({ id: PromOperationId.Absent }),
createFunction({
id: PromOperationId.Acos,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.Acosh,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.Asin,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.Asinh,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.Atan,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.Atanh,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({ id: PromOperationId.Ceil }),
createFunction({
id: PromOperationId.Clamp,
name: 'Clamp',
params: [
{ name: 'Minimum Scalar', type: 'number' },
{ name: 'Maximum Scalar', type: 'number' },
],
defaultParams: [1, 1],
}),
createFunction({
id: PromOperationId.ClampMax,
params: [{ name: 'Maximum Scalar', type: 'number' }],
defaultParams: [1],
}),
createFunction({
id: PromOperationId.ClampMin,
params: [{ name: 'Minimum Scalar', type: 'number' }],
defaultParams: [1],
}),
createFunction({
id: PromOperationId.Cos,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.Cosh,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.CountValues,
params: [{ name: 'Identifier', type: 'string' }],
defaultParams: ['count'],
renderer: functionRendererLeft,
}),
createFunction({
id: PromOperationId.DayOfMonth,
category: PromVisualQueryOperationCategory.Time,
}),
createFunction({
id: PromOperationId.DayOfWeek,
category: PromVisualQueryOperationCategory.Time,
}),
createFunction({
id: PromOperationId.DaysInMonth,
category: PromVisualQueryOperationCategory.Time,
}),
createFunction({ id: PromOperationId.Deg }),
createRangeFunction(PromOperationId.Deriv),
//
createFunction({ id: PromOperationId.Exp }),
createFunction({ id: PromOperationId.Floor }),
createFunction({ id: PromOperationId.Group }),
createFunction({ id: PromOperationId.Hour }),
createFunction({
id: PromOperationId.LabelJoin,
params: [
{
name: 'Destination Label',
type: 'string',
editor: LabelParamEditor,
},
{
name: 'Separator',
type: 'string',
},
{
name: 'Source Label',
type: 'string',
restParam: true,
optional: true,
editor: LabelParamEditor,
},
],
defaultParams: ['', ',', ''],
renderer: labelJoinRenderer,
addOperationHandler: labelJoinAddOperationHandler,
}),
createFunction({ id: PromOperationId.Log10 }),
createFunction({ id: PromOperationId.Log2 }),
createFunction({ id: PromOperationId.Minute }),
createFunction({ id: PromOperationId.Month }),
createFunction({
id: PromOperationId.Pi,
renderer: (model) => `${model.id}()`,
}),
createFunction({
id: PromOperationId.Quantile,
params: [{ name: 'Value', type: 'number' }],
defaultParams: [1],
renderer: functionRendererLeft,
}),
createFunction({ id: PromOperationId.Rad }),
createRangeFunction(PromOperationId.Resets),
createFunction({
id: PromOperationId.Round,
category: PromVisualQueryOperationCategory.Functions,
params: [{ name: 'To Nearest', type: 'number' }],
defaultParams: [1],
}),
createFunction({ id: PromOperationId.Scalar }),
createFunction({ id: PromOperationId.Sgn }),
createFunction({ id: PromOperationId.Sin, category: PromVisualQueryOperationCategory.Trigonometric }),
createFunction({
id: PromOperationId.Sinh,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({ id: PromOperationId.Sort }),
createFunction({ id: PromOperationId.SortDesc }),
createFunction({ id: PromOperationId.Sqrt }),
createFunction({ id: PromOperationId.Stddev }),
createFunction({
id: PromOperationId.Tan,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.Tanh,
category: PromVisualQueryOperationCategory.Trigonometric,
}),
createFunction({
id: PromOperationId.Time,
renderer: (model) => `${model.id}()`,
}),
createFunction({ id: PromOperationId.Timestamp }),
createFunction({
id: PromOperationId.Vector,
params: [{ name: 'Value', type: 'number' }],
defaultParams: [1],
renderer: (model) => `${model.id}(${model.params[0]})`,
}),
createFunction({ id: PromOperationId.Year }),
];
return list;
}
export function createFunction(definition: Partial<QueryBuilderOperationDef>): QueryBuilderOperationDef {
return {
...definition,
id: definition.id!,
name: definition.name ?? getPromAndLokiOperationDisplayName(definition.id!),
params: definition.params ?? [],
defaultParams: definition.defaultParams ?? [],
category: definition.category ?? PromVisualQueryOperationCategory.Functions,
renderer: definition.renderer ?? (definition.params ? functionRendererRight : functionRendererLeft),
addOperationHandler: definition.addOperationHandler ?? defaultAddOperationHandler,
};
}
export function createRangeFunction(name: string, withRateInterval = false): QueryBuilderOperationDef {
return {
id: name,
name: getPromAndLokiOperationDisplayName(name),
params: [getRangeVectorParamDef(withRateInterval)],
defaultParams: [withRateInterval ? '$__rate_interval' : '$__interval'],
alternativesKey: 'range function',
category: PromVisualQueryOperationCategory.RangeFunctions,
renderer: operationWithRangeVectorRenderer,
addOperationHandler: addOperationWithRangeVector,
changeTypeHandler: operationTypeChangedHandlerForRangeFunction,
};
}
function operationTypeChangedHandlerForRangeFunction(
operation: QueryBuilderOperation,
newDef: QueryBuilderOperationDef
) {
// validate current parameter
if (operation.params[0] === '$__rate_interval' && newDef.defaultParams[0] !== '$__rate_interval') {
operation.params = newDef.defaultParams;
} else if (operation.params[0] === '$__interval' && newDef.defaultParams[0] !== '$__interval') {
operation.params = newDef.defaultParams;
}
return operation;
}
export function operationWithRangeVectorRenderer(
model: QueryBuilderOperation,
def: QueryBuilderOperationDef,
innerExpr: string
) {
let rangeVector = (model.params ?? [])[0] ?? '5m';
return `${def.id}(${innerExpr}[${rangeVector}])`;
}
function getSimpleBinaryRenderer(operator: string) {
return function binaryRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
return `${innerExpr} ${operator} ${model.params[0]}`;
};
}
/**
* Since there can only be one operation with range vector this will replace the current one (if one was added )
*/
export function addOperationWithRangeVector(
def: QueryBuilderOperationDef,
query: PromVisualQuery,
modeller: VisualQueryModeller
) {
const newOperation: QueryBuilderOperation = {
id: def.id,
params: def.defaultParams,
};
if (query.operations.length > 0) {
// If operation exists it has to be in the registry so no point to check if it was found
const firstOp = modeller.getOperationDef(query.operations[0].id)!;
if (firstOp.addOperationHandler === addOperationWithRangeVector) {
return {
...query,
operations: [newOperation, ...query.operations.slice(1)],
};
}
}
return {
...query,
operations: [newOperation, ...query.operations],
};
}
function addNestedQueryHandler(def: QueryBuilderOperationDef, query: PromVisualQuery): PromVisualQuery {
return {
...query,
binaryQueries: [
...(query.binaryQueries ?? []),
{
operator: '/',
query,
},
],
};
}
function labelJoinRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
if (typeof model.params[1] !== 'string') {
throw 'The separator must be a string';
}
const separator = `"${model.params[1]}"`;
return `${model.id}(${innerExpr}, "${model.params[0]}", ${separator}, "${model.params.slice(2).join(separator)}")`;
}
function labelJoinAddOperationHandler<T extends QueryWithOperations>(def: QueryBuilderOperationDef, query: T) {
const newOperation: QueryBuilderOperation = {
id: def.id,
params: def.defaultParams,
};
return {
...query,
operations: [...query.operations, newOperation],
};
}