diff --git a/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts b/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts index 56a317419ba..1c2e18f8600 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts @@ -278,4 +278,40 @@ describe('PromQueryModeller', () => { }) ).toBe('metric_a / on(le) metric_b'); }); + it('Can render functions that require a range as a parameter', () => { + expect( + modeller.renderQuery({ + metric: 'metric_a', + labels: [], + operations: [{ id: 'holt_winters', params: ['auto', 0.5, 0.5] }], + }) + ).toBe('holt_winters(metric_a[$__rate_interval], 0.5, 0.5)'); + }); + it('Can render functions that require parameters left of a range', () => { + expect( + modeller.renderQuery({ + metric: 'metric_a', + labels: [], + operations: [{ id: 'quantile_over_time', params: ['auto', 1] }], + }) + ).toBe('quantile_over_time(1, metric_a[$__rate_interval])'); + }); + it('Can render the label_join function', () => { + expect( + modeller.renderQuery({ + metric: 'metric_a', + labels: [], + operations: [{ id: 'label_join', params: ['label_1', ',', 'label_2'] }], + }) + ).toBe('label_join(metric_a, "label_1", ",", "label_2")'); + }); + it('Can render label_join with extra parameters', () => { + expect( + modeller.renderQuery({ + metric: 'metric_a', + labels: [], + operations: [{ id: 'label_join', params: ['label_1', ', ', 'label_2', 'label_3', 'label_4', 'label_5'] }], + }) + ).toBe('label_join(metric_a, "label_1", ", ", "label_2", "label_3", "label_4", "label_5")'); + }); }); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.ts b/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.ts index 96ad85f530e..920fdafa2ae 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.ts @@ -22,6 +22,8 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase innerExpr, addOperationHandler: addNestedQueryHandler, }, + createFunction({ id: PromOperationId.Absent }), + createRangeFunction(PromOperationId.AbsentOverTime), + 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.HoltWinters, + params: [ + getRangeVectorParamDef(), + { name: 'Smoothing Factor', type: 'number' }, + { name: 'Trend Factor', type: 'number' }, + ], + defaultParams: ['auto', 0.5, 0.5], + alternativesKey: 'range function', + category: PromVisualQueryOperationCategory.RangeFunctions, + renderer: rangeRendererRightWithParams, + }), + createFunction({ id: PromOperationId.Hour }), + createRangeFunction(PromOperationId.Idelta), + 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.PredictLinear, + params: [getRangeVectorParamDef(), { name: 'Seconds from now', type: 'number' }], + defaultParams: ['auto', 60], + alternativesKey: 'range function', + category: PromVisualQueryOperationCategory.RangeFunctions, + renderer: rangeRendererRightWithParams, + }), + createFunction({ + id: PromOperationId.Quantile, + params: [{ name: 'Value', type: 'number' }], + defaultParams: [1], + renderer: functionRendererLeft, + }), + createFunction({ + id: PromOperationId.QuantileOverTime, + params: [getRangeVectorParamDef(), { name: 'Quantile', type: 'number' }], + defaultParams: ['auto', 0.5], + alternativesKey: 'range function', + category: PromVisualQueryOperationCategory.RangeFunctions, + renderer: rangeRendererLeftWithParams, + }), + 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; } -function createRangeFunction(name: string): QueryBuilderOperationDef { +export function createFunction(definition: Partial): 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): QueryBuilderOperationDef { return { id: name, name: getPromAndLokiOperationDisplayName(name), @@ -98,7 +301,7 @@ function createRangeFunction(name: string): QueryBuilderOperationDef { }; } -function operationWithRangeVectorRenderer( +export function operationWithRangeVectorRenderer( model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string @@ -174,3 +377,23 @@ function addNestedQueryHandler(def: QueryBuilderOperationDef, query: PromVisualQ ], }; } + +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(def: QueryBuilderOperationDef, query: T) { + const newOperation: QueryBuilderOperation = { + id: def.id, + params: def.defaultParams, + }; + + return { + ...query, + operations: [...query.operations, newOperation], + }; +} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/operationUtils.ts b/public/app/plugins/datasource/prometheus/querybuilder/shared/operationUtils.ts index dc3187e34df..5425c41eaca 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/operationUtils.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/operationUtils.ts @@ -23,6 +23,67 @@ export function functionRendererRight(model: QueryBuilderOperation, def: QueryBu return str + params.join(', ') + ')'; } +function rangeRendererWithParams( + model: QueryBuilderOperation, + def: QueryBuilderOperationDef, + innerExpr: string, + renderLeft: boolean +) { + if (def.params.length < 2) { + throw `Cannot render a function with params of length [${def.params.length}]`; + } + + // First, make sure the first parameter (that is the range vector) is translated if the user selected 'auto' + let rangeVector = (model.params ?? [])[0] ?? 'auto'; + + if (rangeVector === 'auto') { + rangeVector = '$__rate_interval'; + } + + // Next frame the remaining parameters, but get rid of the first one because it's used to move the + // instant vector into a range vector. + const params = renderParams( + { + ...model, + params: model.params.slice(1), + }, + { + ...def, + params: def.params.slice(1), + defaultParams: def.defaultParams.slice(1), + }, + innerExpr + ); + + const str = model.id + '('; + + // Depending on the renderLeft variable, render parameters to the left or right + // renderLeft === true (renderLeft) => (param1, param2, rangeVector[...]) + // renderLeft === false (renderRight) => (rangeVector[...], param1, param2) + if (innerExpr) { + renderLeft ? params.push(`${innerExpr}[${rangeVector}]`) : params.unshift(`${innerExpr}[${rangeVector}]`); + } + + // stick everything together + return str + params.join(', ') + ')'; +} + +export function rangeRendererRightWithParams( + model: QueryBuilderOperation, + def: QueryBuilderOperationDef, + innerExpr: string +) { + return rangeRendererWithParams(model, def, innerExpr, false); +} + +export function rangeRendererLeftWithParams( + model: QueryBuilderOperation, + def: QueryBuilderOperationDef, + innerExpr: string +) { + return rangeRendererWithParams(model, def, innerExpr, true); +} + function renderParams(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) { return (model.params ?? []).map((value, index) => { const paramDef = def.params[index]; diff --git a/public/app/plugins/datasource/prometheus/querybuilder/types.ts b/public/app/plugins/datasource/prometheus/querybuilder/types.ts index 0bd71d3a093..7c639684958 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/types.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/types.ts @@ -20,35 +20,94 @@ export enum PromVisualQueryOperationCategory { RangeFunctions = 'Range functions', Functions = 'Functions', BinaryOps = 'Binary operations', + Trigonometric = 'Trigonometric', + Time = 'Time Functions', } export enum PromOperationId { - HistogramQuantile = 'histogram_quantile', - LabelReplace = 'label_replace', - Ln = 'ln', + Abs = 'abs', + Absent = 'absent', + AbsentOverTime = 'absent_over_time', + Acos = 'acos', + Acosh = 'acosh', + Asin = 'asin', + Asinh = 'asinh', + Atan = 'atan', + Atanh = 'atanh', + Avg = 'avg', + AvgOverTime = 'avg_over_time', + BottomK = 'bottomk', + Ceil = 'ceil', Changes = 'changes', - Rate = 'rate', - Irate = 'irate', - Increase = 'increase', + Clamp = 'clamp', + ClampMax = 'clamp_max', + ClampMin = 'clamp_min', + Cos = 'cos', + Cosh = 'cosh', + Count = 'count', + CountOverTime = 'count_over_time', + CountScalar = 'count_scalar', + CountValues = 'count_values', + DayOfMonth = 'day_of_month', + DayOfWeek = 'day_of_week', + DaysInMonth = 'days_in_month', + Deg = 'deg', Delta = 'delta', + Deriv = 'deriv', + DropCommonLabels = 'drop_common_labels', + Exp = 'exp', + Floor = 'floor', + Group = 'group', + HistogramQuantile = 'histogram_quantile', + HoltWinters = 'holt_winters', + Hour = 'hour', + Idelta = 'idelta', + Increase = 'increase', + Irate = 'irate', + LabelJoin = 'label_join', + LabelReplace = 'label_replace', + Last = 'last', + LastOverTime = 'last_over_time', + Ln = 'ln', + Log10 = 'log10', + Log2 = 'log2', + Max = 'max', + MaxOverTime = 'max_over_time', + Min = 'min', + MinOverTime = 'min_over_time', + Minute = 'minute', + Month = 'month', + Pi = 'pi', + PredictLinear = 'predict_linear', + Present = 'present', + PresentOverTime = 'present_over_time', + Quantile = 'quantile', + QuantileOverTime = 'quantile_over_time', + Rad = 'rad', + Rate = 'rate', + Resets = 'resets', + Round = 'round', + Scalar = 'scalar', + Sgn = 'sgn', + Sin = 'sin', + Sinh = 'sinh', + Sort = 'sort', + SortDesc = 'sort_desc', + Sqrt = 'sqrt', + Stddev = 'stddev', + StddevOverTime = 'stddev_over_time', + Sum = 'sum', + SumOverTime = 'sum_over_time', + Tan = 'tan', + Tanh = 'tanh', + Time = 'time', + Timestamp = 'timestamp', + Topk = 'topk', + Vector = 'vector', + Year = 'year', MultiplyBy = '__multiply_by', DivideBy = '__divide_by', NestedQuery = '__nested_query', - Sum = 'sum', - Avg = 'avg', - Min = 'min', - Max = 'max', - Count = 'count', - Topk = 'topk', - SumOverTime = 'sum_over_time', - AvgOverTime = 'avg_over_time', - MinOverTime = 'min_over_time', - MaxOverTime = 'max_over_time', - CountOverTime = 'count_over_time', - LastOverTime = 'last_over_time', - PresentOverTime = 'present_over_time', - StddevOverTime = 'stddev_over_time', - StdvarOverTime = 'stdvar_over_time', } export interface PromQueryPattern {