diff --git a/packages/grafana-data/src/transformations/transformers/histogram.test.ts b/packages/grafana-data/src/transformations/transformers/histogram.test.ts index 64a952d2196..7bbd03397a0 100644 --- a/packages/grafana-data/src/transformations/transformers/histogram.test.ts +++ b/packages/grafana-data/src/transformations/transformers/histogram.test.ts @@ -22,6 +22,14 @@ describe('histogram frames frames', () => { fields: [{ name: 'C', type: FieldType.number, values: [5, 6, 7, 8, 9] }], }); + const series3 = toDataFrame({ + fields: [{ name: 'D', type: FieldType.number, values: [1, 2, 3, null, null] }], + }); + + const series4 = toDataFrame({ + fields: [{ name: 'E', type: FieldType.number, values: [4, 5, null, 6, null], config: { noValue: '0' } }], + }); + const out = histogramFieldsToFrame(buildHistogram([series1, series2])!); expect( out.fields.map((f) => ({ @@ -188,5 +196,87 @@ describe('histogram frames frames', () => { }, ] `); + + // NULLs filtering test + const out3 = histogramFieldsToFrame(buildHistogram([series3])!); + expect( + out3.fields.map((f) => ({ + name: f.name, + values: f.values, + })) + ).toMatchInlineSnapshot(` + [ + { + "name": "xMin", + "values": [ + 1, + 2, + 3, + ], + }, + { + "name": "xMax", + "values": [ + 2, + 3, + 4, + ], + }, + { + "name": "D", + "values": [ + 1, + 1, + 1, + ], + }, + ] + `); + + // noValue nulls test + const out4 = histogramFieldsToFrame(buildHistogram([series4])!); + expect( + out4.fields.map((f) => ({ + name: f.name, + values: f.values, + config: f.config, + })) + ).toMatchInlineSnapshot(` + [ + { + "config": {}, + "name": "xMin", + "values": [ + 0, + 4, + 5, + 6, + ], + }, + { + "config": {}, + "name": "xMax", + "values": [ + 1, + 5, + 6, + 7, + ], + }, + { + "config": { + "noValue": "0", + "unit": undefined, + }, + "name": "E", + "values": [ + 2, + 1, + 1, + 1, + ], + }, + ] + `); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/histogram.ts b/packages/grafana-data/src/transformations/transformers/histogram.ts index 3f21dcdd9ce..1f333048e8e 100644 --- a/packages/grafana-data/src/transformations/transformers/histogram.ts +++ b/packages/grafana-data/src/transformations/transformers/histogram.ts @@ -8,6 +8,7 @@ import { roundDecimals } from '../../utils'; import { DataTransformerID } from './ids'; import { AlignedData, join } from './joinDataFrames'; +import { nullToValueField } from './nulls/nullToValue'; import { transformationsVariableSupport } from './utils'; /** @@ -334,6 +335,26 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform let bucketCount = options?.bucketCount ?? DEFAULT_BUCKET_COUNT; let bucketOffset = options?.bucketOffset ?? 0; + // replace or filter nulls from numeric fields + frames = frames.map((frame) => { + return { + ...frame, + fields: frame.fields.map((field) => { + if (field.type === FieldType.number) { + const noValue = Number(field.config.noValue); + + if (!Number.isNaN(noValue)) { + field = nullToValueField(field, noValue); + } else { + field = { ...field, values: field.values.filter((v) => v != null) }; + } + } + + return field; + }), + }; + }); + // if bucket size is auto, try to calc from all numeric fields if (!bucketSize || bucketSize < 0) { let allValues: number[] = []; @@ -347,8 +368,6 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform } } - allValues = allValues.filter((v) => v != null); - allValues.sort((a, b) => a - b); let smallestDelta = Infinity; diff --git a/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.ts b/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.ts index 4c3ba5a81e6..a2d2eeb88c7 100644 --- a/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.ts +++ b/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.ts @@ -1,27 +1,31 @@ -import { DataFrame } from '../../../types'; +import { DataFrame, Field } from '../../../types'; export function nullToValue(frame: DataFrame) { return { ...frame, fields: frame.fields.map((field) => { - const noValue = +field.config?.noValue!; + const noValue = Number(field.config.noValue); if (!Number.isNaN(noValue)) { - const transformedVals = field.values.slice(); - - for (let i = 0; i < transformedVals.length; i++) { - if (transformedVals[i] === null) { - transformedVals[i] = noValue; - } - } - - return { - ...field, - values: transformedVals, - }; + return nullToValueField(field, noValue); } else { return field; } }), }; } + +export function nullToValueField(field: Field, noValue: number) { + const transformedVals = field.values.slice(); + + for (let i = 0; i < transformedVals.length; i++) { + if (transformedVals[i] === null) { + transformedVals[i] = noValue; + } + } + + return { + ...field, + values: transformedVals, + }; +}