diff --git a/packages/grafana-prometheus/src/querybuilder/aggregations.test.ts b/packages/grafana-prometheus/src/querybuilder/aggregations.test.ts new file mode 100644 index 00000000000..56dc549c1d3 --- /dev/null +++ b/packages/grafana-prometheus/src/querybuilder/aggregations.test.ts @@ -0,0 +1,55 @@ +import { getAggregationOperations } from './aggregations'; +import { QueryBuilderOperation } from './shared/types'; +import { PromOperationId } from './types'; + +describe('getAggregationOperations', () => { + it('returns a list containing all aggregation IDs', () => { + const ops = getAggregationOperations(); + const ids = ops.map((o) => o.id); + + expect(ids).toContain(PromOperationId.Sum); + expect(ids).toContain(PromOperationId.Avg); + expect(ids).toContain(PromOperationId.Min); + expect(ids).toContain(PromOperationId.Max); + expect(ids).toContain(PromOperationId.Count); + expect(ids).toContain(PromOperationId.Group); + expect(ids).toContain(PromOperationId.Stddev); + expect(ids).toContain(PromOperationId.Stdvar); + + // aggregations with params + expect(ids).toContain(PromOperationId.TopK); + expect(ids).toContain(PromOperationId.BottomK); + expect(ids).toContain(PromOperationId.CountValues); + expect(ids).toContain(PromOperationId.Quantile); + expect(ids).toContain(PromOperationId.LimitK); + expect(ids).toContain(PromOperationId.LimitRatio); + + // over-time range aggregations + expect(ids).toContain(PromOperationId.SumOverTime); + expect(ids).toContain(PromOperationId.AvgOverTime); + expect(ids).toContain(PromOperationId.MinOverTime); + expect(ids).toContain(PromOperationId.MaxOverTime); + expect(ids).toContain(PromOperationId.CountOverTime); + expect(ids).toContain(PromOperationId.LastOverTime); + expect(ids).toContain(PromOperationId.PresentOverTime); + expect(ids).toContain(PromOperationId.AbsentOverTime); + expect(ids).toContain(PromOperationId.StddevOverTime); + }); + + it('includes over-time range functions with correct renderer behavior', () => { + const ops = getAggregationOperations(); + const sumOverTime = ops.find((o) => o.id === PromOperationId.SumOverTime)!; + expect(sumOverTime).toBeDefined(); + expect(sumOverTime.params.length).toBeGreaterThan(0); + + // call renderer with default param + const model: QueryBuilderOperation = { id: sumOverTime.id, params: sumOverTime.defaultParams }; + const rendered = sumOverTime.renderer(model, sumOverTime, 'metric'); + expect(rendered).toBe(`${PromOperationId.SumOverTime}(metric[$__interval])`); + + // call renderer with custom param + const modelCustom: QueryBuilderOperation = { id: sumOverTime.id, params: ['5m'] }; + const renderedCustom = sumOverTime.renderer(modelCustom, sumOverTime, 'metric'); + expect(renderedCustom).toBe(`${PromOperationId.SumOverTime}(metric[5m])`); + }); +}); diff --git a/packages/grafana-prometheus/src/querybuilder/aggregations.ts b/packages/grafana-prometheus/src/querybuilder/aggregations.ts index 2eaa1f92074..a2e2cf6d271 100644 --- a/packages/grafana-prometheus/src/querybuilder/aggregations.ts +++ b/packages/grafana-prometheus/src/querybuilder/aggregations.ts @@ -17,6 +17,8 @@ export function getAggregationOperations(): QueryBuilderOperationDef[] { ...createAggregationOperation(PromOperationId.Max), ...createAggregationOperation(PromOperationId.Count), ...createAggregationOperation(PromOperationId.Group), + ...createAggregationOperation(PromOperationId.Stddev), + ...createAggregationOperation(PromOperationId.Stdvar), ...createAggregationOperationWithParam(PromOperationId.TopK, { params: [{ name: 'K-value', type: 'number' }], defaultParams: [5], @@ -33,6 +35,14 @@ export function getAggregationOperations(): QueryBuilderOperationDef[] { params: [{ name: 'Value', type: 'number' }], defaultParams: [1], }), + ...createAggregationOperationWithParam(PromOperationId.LimitK, { + params: [{ name: 'K-value', type: 'number' }], + defaultParams: [5], + }), + ...createAggregationOperationWithParam(PromOperationId.LimitRatio, { + params: [{ name: 'Ratio', type: 'number' }], + defaultParams: [1], + }), createAggregationOverTime(PromOperationId.SumOverTime), createAggregationOverTime(PromOperationId.AvgOverTime), createAggregationOverTime(PromOperationId.MinOverTime), diff --git a/packages/grafana-prometheus/src/querybuilder/operations.test.ts b/packages/grafana-prometheus/src/querybuilder/operations.test.ts new file mode 100644 index 00000000000..b60350e8feb --- /dev/null +++ b/packages/grafana-prometheus/src/querybuilder/operations.test.ts @@ -0,0 +1,68 @@ +import { getOperationDefinitions, addOperationWithRangeVector } from './operations'; +import { QueryBuilderOperation, VisualQueryModeller } from './shared/types'; +import { PromOperationId, PromVisualQuery } from './types'; + +describe('getOperationDefinitions', () => { + it('returns a list containing operation IDs', () => { + const ops = getOperationDefinitions(); + const ids = ops.map((o) => o.id); + + expect(ids).toContain(PromOperationId.HistogramQuantile); + expect(ids).toContain(PromOperationId.Rate); + expect(ids).toContain(PromOperationId.PredictLinear); + expect(ids).toContain(PromOperationId.QuantileOverTime); + expect(ids).toContain(PromOperationId.LabelJoin); + expect(ids).toContain(PromOperationId.Vector); + }); + + it('range function renderer uses default and custom range vector params', () => { + const ops = getOperationDefinitions(); + const rate = ops.find((o) => o.id === PromOperationId.Rate)!; + + expect(rate).toBeDefined(); + + const modelDefault: QueryBuilderOperation = { id: rate.id, params: rate.defaultParams }; + const renderedDefault = rate.renderer(modelDefault, rate, 'metric'); + expect(renderedDefault).toBe(`${PromOperationId.Rate}(metric[$__rate_interval])`); + + const modelCustom: QueryBuilderOperation = { id: rate.id, params: ['5m'] }; + const renderedCustom = rate.renderer(modelCustom, rate, 'metric'); + expect(renderedCustom).toBe(`${PromOperationId.Rate}(metric[5m])`); + }); + + it('changeTypeHandler updates operation params when rate/interval default changes', () => { + const ops = getOperationDefinitions(); + const doubleExp = ops.find((o) => o.id === PromOperationId.DoubleExponentialSmoothing)!; + expect(doubleExp).toBeDefined(); + // simulate an operation currently using $__rate_interval + const operation: QueryBuilderOperation = { id: doubleExp.id, params: ['$__rate_interval'] }; + + // create a new definition that uses $__interval instead + const newDef = { ...doubleExp, defaultParams: ['$__interval'] }; + + if (doubleExp.changeTypeHandler) { + const updated = doubleExp.changeTypeHandler(operation, newDef); + expect(updated.params).toEqual(newDef.defaultParams); + } + }); + + it('addOperationWithRangeVector inserts a range op at the front of the query', () => { + const ops = getOperationDefinitions(); + const changes = ops.find((o) => o.id === PromOperationId.Changes)!; + const query: PromVisualQuery = { operations: [], metric: 'metric', labels: [] }; + const modeller = {} as VisualQueryModeller; + + const result = addOperationWithRangeVector(changes, query, modeller); + expect(result.operations[0].id).toBe(changes.id); + }); + + it('vector renderer renders parameter as value', () => { + const ops = getOperationDefinitions(); + const vector = ops.find((o) => o.id === PromOperationId.Vector)!; + expect(vector).toBeDefined(); + + const model: QueryBuilderOperation = { id: vector.id, params: [42] }; + const rendered = vector.renderer(model, vector, ''); + expect(rendered).toBe(`${PromOperationId.Vector}(42)`); + }); +}); diff --git a/packages/grafana-prometheus/src/querybuilder/operations.ts b/packages/grafana-prometheus/src/querybuilder/operations.ts index 3f9e3ac21d4..f20676308de 100644 --- a/packages/grafana-prometheus/src/querybuilder/operations.ts +++ b/packages/grafana-prometheus/src/querybuilder/operations.ts @@ -258,7 +258,6 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] { createFunction({ id: PromOperationId.Sort }), createFunction({ id: PromOperationId.SortDesc }), createFunction({ id: PromOperationId.Sqrt }), - createFunction({ id: PromOperationId.Stddev }), createFunction({ id: PromOperationId.Tan, category: PromVisualQueryOperationCategory.Trigonometric, diff --git a/packages/grafana-prometheus/src/querybuilder/types.ts b/packages/grafana-prometheus/src/querybuilder/types.ts index c2bc18bc2e5..322b3a0266c 100644 --- a/packages/grafana-prometheus/src/querybuilder/types.ts +++ b/packages/grafana-prometheus/src/querybuilder/types.ts @@ -100,6 +100,8 @@ export enum PromOperationId { LabelReplace = 'label_replace', Last = 'last', LastOverTime = 'last_over_time', + LimitK = 'limitk', + LimitRatio = 'limit_ratio', Ln = 'ln', Log10 = 'log10', Log2 = 'log2', @@ -128,6 +130,7 @@ export enum PromOperationId { Sqrt = 'sqrt', Stddev = 'stddev', StddevOverTime = 'stddev_over_time', + Stdvar = 'stdvar', Sum = 'sum', SumOverTime = 'sum_over_time', Tan = 'tan',