Skip to content

Commit

Permalink
Timeseries: Fix null pointer when matching fill below to field (grafa…
Browse files Browse the repository at this point in the history
…na#58030)

* Fix timeseries fill below to error

* wip

* modifications

* add tests for fillBelowTo option
  • Loading branch information
mdvictor authored Nov 6, 2022
1 parent bdb8fe3 commit d673c88
Show file tree
Hide file tree
Showing 2 changed files with 285 additions and 2 deletions.
275 changes: 275 additions & 0 deletions packages/grafana-ui/src/components/TimeSeries/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import { ArrayVector, FieldType } from '@grafana/data';
import { getTheme } from '@grafana/ui';

import { preparePlotConfigBuilder } from './utils';

describe('when fill below to option is used', () => {
// eslint-disable-next-line
let eventBus: any;
// eslint-disable-next-line
let renderers: any[];
// eslint-disable-next-line
let tests: any;

beforeEach(() => {
eventBus = {
publish: jest.fn(),
getStream: jest.fn(),
subscribe: jest.fn(),
removeAllListeners: jest.fn(),
newScopedBus: jest.fn(),
};
renderers = [];

tests = [
{
alignedFrame: {
fields: [
{
config: {},
values: new ArrayVector([1667406900000, 1667407170000, 1667407185000]),
name: 'Time',
state: { multipleFrames: true, displayName: 'Time', origin: { fieldIndex: 0, frameIndex: 0 } },
type: FieldType.time,
},
{
config: { displayNameFromDS: 'Test1', custom: { fillBelowTo: 'Test2' }, min: 0, max: 100 },
values: new ArrayVector([1, 2, 3]),
name: 'Value',
state: { multipleFrames: true, displayName: 'Test1', origin: { fieldIndex: 1, frameIndex: 0 } },
type: FieldType.number,
},
{
config: { displayNameFromDS: 'Test2', min: 0, max: 100 },
values: new ArrayVector([4, 5, 6]),
name: 'Value',
state: { multipleFrames: true, displayName: 'Test2', origin: { fieldIndex: 1, frameIndex: 1 } },
type: FieldType.number,
},
],
length: 3,
},
allFrames: [
{
name: 'Test1',
refId: 'A',
fields: [
{
config: {},
values: new ArrayVector([1667406900000, 1667407170000, 1667407185000]),
name: 'Time',
state: { multipleFrames: true, displayName: 'Time', origin: { fieldIndex: 0, frameIndex: 0 } },
type: FieldType.time,
},
{
config: { displayNameFromDS: 'Test1', custom: { fillBelowTo: 'Test2' }, min: 0, max: 100 },
values: new ArrayVector([1, 2, 3]),
name: 'Value',
state: { multipleFrames: true, displayName: 'Test1', origin: { fieldIndex: 1, frameIndex: 0 } },
type: FieldType.number,
},
],
length: 2,
},
{
name: 'Test2',
refId: 'B',
fields: [
{
config: {},
values: new ArrayVector([1667406900000, 1667407170000, 1667407185000]),
name: 'Time',
state: { multipleFrames: true, displayName: 'Time', origin: { fieldIndex: 0, frameIndex: 1 } },
type: FieldType.time,
},
{
config: { displayNameFromDS: 'Test2', min: 0, max: 100 },
values: new ArrayVector([1, 2, 3]),
name: 'Value',
state: { multipleFrames: true, displayName: 'Test2', origin: { fieldIndex: 1, frameIndex: 1 } },
type: FieldType.number,
},
],
length: 2,
},
],
expectedResult: 1,
},
{
alignedFrame: {
fields: [
{
config: {},
values: new ArrayVector([1667406900000, 1667407170000, 1667407185000]),
name: 'time',
state: { multipleFrames: true, displayName: 'time', origin: { fieldIndex: 0, frameIndex: 0 } },
type: FieldType.time,
},
{
config: { custom: { fillBelowTo: 'below_value1' } },
values: new ArrayVector([1, 2, 3]),
name: 'value1',
state: { multipleFrames: true, displayName: 'value1', origin: { fieldIndex: 1, frameIndex: 0 } },
type: FieldType.number,
},
{
config: { custom: { fillBelowTo: 'below_value2' } },
values: new ArrayVector([4, 5, 6]),
name: 'value2',
state: { multipleFrames: true, displayName: 'value2', origin: { fieldIndex: 2, frameIndex: 0 } },
type: FieldType.number,
},
{
config: {},
values: new ArrayVector([4, 5, 6]),
name: 'below_value1',
state: { multipleFrames: true, displayName: 'below_value1', origin: { fieldIndex: 1, frameIndex: 1 } },
type: FieldType.number,
},
{
config: {},
values: new ArrayVector([4, 5, 6]),
name: 'below_value2',
state: { multipleFrames: true, displayName: 'below_value2', origin: { fieldIndex: 2, frameIndex: 1 } },
type: FieldType.number,
},
],
length: 5,
},
allFrames: [
{
refId: 'A',
fields: [
{
config: {},
values: new ArrayVector([1667406900000, 1667407170000, 1667407185000]),
name: 'time',
state: { multipleFrames: true, displayName: 'time', origin: { fieldIndex: 0, frameIndex: 0 } },
type: FieldType.time,
},
{
config: { custom: { fillBelowTo: 'below_value1' } },
values: new ArrayVector([1, 2, 3]),
name: 'value1',
state: { multipleFrames: true, displayName: 'value1', origin: { fieldIndex: 1, frameIndex: 0 } },
type: FieldType.number,
},
{
config: { custom: { fillBelowTo: 'below_value2' } },
values: new ArrayVector([4, 5, 6]),
name: 'value2',
state: { multipleFrames: true, displayName: 'value2', origin: { fieldIndex: 2, frameIndex: 0 } },
type: FieldType.number,
},
],
length: 3,
},
{
refId: 'B',
fields: [
{
config: {},
values: new ArrayVector([1667406900000, 1667407170000, 1667407185000]),
name: 'time',
state: { multipleFrames: true, displayName: 'time', origin: { fieldIndex: 0, frameIndex: 1 } },
type: FieldType.time,
},
{
config: {},
values: new ArrayVector([4, 5, 6]),
name: 'below_value1',
state: { multipleFrames: true, displayName: 'below_value1', origin: { fieldIndex: 1, frameIndex: 1 } },
type: FieldType.number,
},
{
config: {},
values: new ArrayVector([4, 5, 6]),
name: 'below_value2',
state: { multipleFrames: true, displayName: 'below_value2', origin: { fieldIndex: 2, frameIndex: 1 } },
type: FieldType.number,
},
],
length: 3,
},
],
expectedResult: 2,
},
];
});

it('should verify if fill below to is set then builder bands are set', () => {
for (const test of tests) {
const builder = preparePlotConfigBuilder({
frame: test.alignedFrame,
//@ts-ignore
theme: getTheme(),
timeZones: ['browser'],
getTimeRange: jest.fn(),
eventBus,
sync: jest.fn(),
allFrames: test.allFrames,
renderers,
});

//@ts-ignore
expect(builder.bands.length).toBe(test.expectedResult);
}
});

it('should verify if fill below to is not set then builder bands are empty', () => {
tests[0].alignedFrame.fields[1].config.custom.fillBelowTo = undefined;
tests[0].allFrames[0].fields[1].config.custom.fillBelowTo = undefined;
tests[1].alignedFrame.fields[1].config.custom.fillBelowTo = undefined;
tests[1].alignedFrame.fields[2].config.custom.fillBelowTo = undefined;
tests[1].allFrames[0].fields[1].config.custom.fillBelowTo = undefined;
tests[1].allFrames[0].fields[2].config.custom.fillBelowTo = undefined;
tests[0].expectedResult = 0;
tests[1].expectedResult = 0;

for (const test of tests) {
const builder = preparePlotConfigBuilder({
frame: test.alignedFrame,
//@ts-ignore
theme: getTheme(),
timeZones: ['browser'],
getTimeRange: jest.fn(),
eventBus,
sync: jest.fn(),
allFrames: test.allFrames,
renderers,
});

//@ts-ignore
expect(builder.bands.length).toBe(test.expectedResult);
}
});

it('should verify if fill below to is set and field name is overriden then builder bands are set', () => {
tests[0].alignedFrame.fields[2].config.displayName = 'newName';
tests[0].alignedFrame.fields[2].state.displayName = 'newName';
tests[0].allFrames[1].fields[1].config.displayName = 'newName';
tests[0].allFrames[1].fields[1].state.displayName = 'newName';

tests[1].alignedFrame.fields[3].config.displayName = 'newName';
tests[1].alignedFrame.fields[3].state.displayName = 'newName';
tests[1].allFrames[1].fields[1].config.displayName = 'newName';
tests[1].allFrames[1].fields[1].state.displayName = 'newName';

for (const test of tests) {
const builder = preparePlotConfigBuilder({
frame: test.alignedFrame,
//@ts-ignore
theme: getTheme(),
timeZones: ['browser'],
getTimeRange: jest.fn(),
eventBus,
sync: jest.fn(),
allFrames: test.allFrames,
renderers,
});

//@ts-ignore
expect(builder.bands.length).toBe(test.expectedResult);
}
});
});
12 changes: 10 additions & 2 deletions packages/grafana-ui/src/components/TimeSeries/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,16 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
}

if (customConfig.fillBelowTo) {
const fillBelowToField = frame.fields.find((f) => f.name === customConfig.fillBelowTo);
const fillBelowDispName = getFieldDisplayName(fillBelowToField!, frame, allFrames);
const fillBelowToField = frame.fields.find(
(f) =>
customConfig.fillBelowTo === f.name ||
customConfig.fillBelowTo === f.config?.displayNameFromDS ||
customConfig.fillBelowTo === getFieldDisplayName(f, frame, allFrames)
);

const fillBelowDispName = fillBelowToField
? getFieldDisplayName(fillBelowToField, frame, allFrames)
: customConfig.fillBelowTo;

const t = indexByName.get(dispName);
const b = indexByName.get(fillBelowDispName);
Expand Down

0 comments on commit d673c88

Please sign in to comment.