Table: Fix case where undefined data crashes the visualization (#80498)

This commit is contained in:
Nathan Marrs 2024-01-23 14:59:22 -07:00 committed by GitHub
parent a7b58a7cdb
commit cd2abce914
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 47 additions and 26 deletions

View File

@ -179,6 +179,9 @@ exports[`better eslint`] = {
"packages/grafana-data/src/themes/createColors.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-data/src/transformations/fieldReducer.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"packages/grafana-data/src/transformations/matchers/valueMatchers/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]

View File

@ -124,7 +124,7 @@ describe('Global MinMax', () => {
});
});
describe('when value values are zeo', () => {
describe('when values are zero', () => {
it('then global min max should be correct', () => {
const frame = toDataFrame({
fields: [

View File

@ -3,7 +3,7 @@ import { difference } from 'lodash';
import { createDataFrame, guessFieldTypeFromValue } from '../dataframe/processDataFrame';
import { Field, FieldType, NullValueMode } from '../types/index';
import { fieldReducers, ReducerID, reduceField } from './fieldReducer';
import { fieldReducers, ReducerID, reduceField, defaultCalcs } from './fieldReducer';
/**
* Run a reducer and get back the value
@ -63,6 +63,16 @@ describe('Stats Calculators', () => {
expect(stats.count).toEqual(2);
});
it('should handle undefined field data without crashing', () => {
// eslint-ignore @typescript-eslint/no-explicit-any
const stats = reduceField({
field: { name: 'a', values: undefined as any, config: {}, type: FieldType.number },
reducers: [ReducerID.first, ReducerID.last, ReducerID.mean, ReducerID.count],
});
expect(stats).toEqual(defaultCalcs);
});
it('should support a single stat also', () => {
basicTable.fields[0].state = undefined; // clear the cache
const stats = reduceField({

View File

@ -84,7 +84,7 @@ export function reduceField(options: ReduceFieldOptions): FieldCalcs {
// Return early for empty series
// This lets the concrete implementations assume at least one row
const data = field.values;
if (data.length < 1) {
if (data && data.length < 1) {
const calcs: FieldCalcs = { ...field.state.calcs };
for (const reducer of queue) {
calcs[reducer.id] = reducer.emptyInputResult !== null ? reducer.emptyInputResult : null;
@ -271,33 +271,41 @@ export const fieldReducers = new Registry<FieldReducerInfo>(() => [
},
]);
export function doStandardCalcs(field: Field, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs {
const calcs: FieldCalcs = {
sum: 0,
max: -Number.MAX_VALUE,
min: Number.MAX_VALUE,
logmin: Number.MAX_VALUE,
mean: null,
last: null,
first: null,
lastNotNull: null,
firstNotNull: null,
count: 0,
nonNullCount: 0,
allIsNull: true,
allIsZero: true,
range: null,
diff: null,
delta: 0,
step: Number.MAX_VALUE,
diffperc: 0,
// Used for test cases
export const defaultCalcs: FieldCalcs = {
sum: 0,
max: -Number.MAX_VALUE,
min: Number.MAX_VALUE,
logmin: Number.MAX_VALUE,
mean: null,
last: null,
first: null,
lastNotNull: null,
firstNotNull: null,
count: 0,
nonNullCount: 0,
allIsNull: true,
allIsZero: true,
range: null,
diff: null,
delta: 0,
step: Number.MAX_VALUE,
diffperc: 0,
// Just used for calculations -- not exposed as a stat
previousDeltaUp: true,
};
// Just used for calculations -- not exposed as a stat
previousDeltaUp: true,
};
export function doStandardCalcs(field: Field, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs {
const calcs: FieldCalcs = { ...defaultCalcs };
const data = field.values;
// early return for undefined / empty series
if (!data) {
return calcs;
}
const isNumberField = field.type === FieldType.number || field.type === FieldType.time;
for (let i = 0; i < data.length; i++) {