import { createTheme } from '../themes/createTheme'; import { FieldType } from '../types/dataFrame'; import { TimeRange } from '../types/time'; import { createDataFrame, toDataFrame } from './processDataFrame'; import { anySeriesWithTimeField, addRow, alignTimeRangeCompareData, shouldAlignTimeCompare } from './utils'; describe('anySeriesWithTimeField', () => { describe('single frame', () => { test('without time field', () => { const frameA = toDataFrame({ fields: [ { name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] }, { name: 'value', type: FieldType.number, values: [1, 2, 3] }, ], }); expect(anySeriesWithTimeField([frameA])).toBeFalsy(); }); test('with time field', () => { const frameA = toDataFrame({ fields: [ { name: 'time', type: FieldType.time, values: [100, 200, 300] }, { name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] }, { name: 'value', type: FieldType.number, values: [1, 2, 3] }, ], }); expect(anySeriesWithTimeField([frameA])).toBeTruthy(); }); }); describe('multiple frames', () => { test('without time field', () => { const frameA = toDataFrame({ fields: [ { name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] }, { name: 'value', type: FieldType.number, values: [1, 2, 3] }, ], }); const frameB = toDataFrame({ fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3] }], }); expect(anySeriesWithTimeField([frameA, frameB])).toBeFalsy(); }); test('with time field in any frame', () => { const frameA = toDataFrame({ fields: [ { name: 'time', type: FieldType.time, values: [100, 200, 300] }, { name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] }, { name: 'value', type: FieldType.number, values: [1, 2, 3] }, ], }); const frameB = toDataFrame({ fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3] }], }); const frameC = toDataFrame({ fields: [{ name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] }], }); expect(anySeriesWithTimeField([frameA, frameB, frameC])).toBeTruthy(); }); test('with time field in a all frames', () => { const frameA = toDataFrame({ fields: [ { name: 'time', type: FieldType.time, values: [100, 200, 300] }, { name: 'value', type: FieldType.number, values: [1, 2, 3] }, ], }); const frameB = toDataFrame({ fields: [ { name: 'time', type: FieldType.time, values: [100, 200, 300] }, { name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] }, { name: 'value', type: FieldType.number, values: [1, 2, 3] }, ], }); expect(anySeriesWithTimeField([frameA, frameB])).toBeTruthy(); }); }); }); describe('addRow', () => { const frame = createDataFrame({ fields: [ { name: 'name', type: FieldType.string }, { name: 'date', type: FieldType.time }, { name: 'number', type: FieldType.number }, ], }); const date = Date.now(); it('adds row to data frame as object', () => { addRow(frame, { name: 'A', date, number: 1 }); expect(frame.fields[0].values[0]).toBe('A'); expect(frame.fields[1].values[0]).toBe(date); expect(frame.fields[2].values[0]).toBe(1); expect(frame.length).toBe(1); }); it('adds row to data frame as array', () => { addRow(frame, ['B', date, 42]); expect(frame.fields[0].values[1]).toBe('B'); expect(frame.fields[1].values[1]).toBe(date); expect(frame.fields[2].values[1]).toBe(42); expect(frame.length).toBe(2); }); }); describe('alignTimeRangeCompareData', () => { const ONE_DAY_MS = 24 * 60 * 60 * 1000; // 86400000ms const ONE_WEEK_MS = 7 * ONE_DAY_MS; // 604800000ms it('should align time field values with positive diff (1 day)', () => { const frame = toDataFrame({ fields: [ { name: 'time', type: FieldType.time, values: [1000, 2000, 3000] }, { name: 'value', type: FieldType.number, values: [10, 20, 30] }, ], }); alignTimeRangeCompareData(frame, ONE_DAY_MS, createTheme()); expect(frame.fields[0].values).toEqual([ONE_DAY_MS + 1000, ONE_DAY_MS + 2000, ONE_DAY_MS + 3000]); expect(frame.fields[1].values).toEqual([10, 20, 30]); // non-time fields unchanged }); it('should align time field values with negative diff (1 week)', () => { const frame = toDataFrame({ fields: [ { name: 'time', type: FieldType.time, values: [1000, 2000, 3000] }, { name: 'value', type: FieldType.number, values: [10, 20, 30] }, ], }); alignTimeRangeCompareData(frame, -ONE_WEEK_MS, createTheme()); // When diff is negative, function does v - diff, so v - (-ONE_WEEK_MS) = v + ONE_WEEK_MS expect(frame.fields[0].values).toEqual([ONE_WEEK_MS + 1000, ONE_WEEK_MS + 2000, ONE_WEEK_MS + 3000]); }); it('should apply timeCompare config', () => { const frame = toDataFrame({ fields: [ { name: 'time', type: FieldType.time, values: [1000, 2000] }, { name: 'value', type: FieldType.number, values: [10, 20] }, ], }); alignTimeRangeCompareData(frame, ONE_DAY_MS, createTheme()); frame.fields.forEach((field) => { expect(field.config.custom?.timeCompare).toEqual({ diffMs: ONE_DAY_MS, isTimeShiftQuery: true, }); }); }); it('should preserve existing config when merging', () => { const frame = toDataFrame({ fields: [ { name: 'value', type: FieldType.number, values: [10, 20], config: { displayName: 'My Display Name', custom: { existingProperty: 'existingValue' }, }, }, ], }); alignTimeRangeCompareData(frame, ONE_WEEK_MS, createTheme()); expect(frame.fields[0].config.displayName).toBe('My Display Name'); expect(frame.fields[0].config.custom?.existingProperty).toBe('existingValue'); expect(frame.fields[0].config.custom?.timeCompare?.diffMs).toBe(ONE_WEEK_MS); }); }); describe('shouldAlignTimeCompare', () => { const TIME_VALUES_A = [1000, 2000, 3000]; const TIME_VALUES_B = [5000, 6000, 7000]; const ORIGINAL_VALUES = [10, 20, 30]; const COMPARE_VALUES = [15, 25, 35]; const mockTimeRange: TimeRange = { from: { valueOf: () => 4000 }, to: { valueOf: () => 8000 }, raw: { from: 'now-1h', to: 'now' }, } as TimeRange; it('should return true when compare first time is before time range', () => { const originalFrame = toDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: TIME_VALUES_A }, { name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }, ], }); const compareFrame = toDataFrame({ refId: 'A-compare', fields: [ { name: 'time', type: FieldType.time, values: TIME_VALUES_A }, { name: 'value', type: FieldType.number, values: COMPARE_VALUES }, ], meta: { timeCompare: { isTimeShiftQuery: true, diffMs: 86400000, }, }, }); const allFrames = [originalFrame, compareFrame]; expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(true); }); it('should return false when compare first time is after time range', () => { const originalFrame = toDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: TIME_VALUES_A }, { name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }, ], }); const compareFrame = toDataFrame({ refId: 'A-compare', fields: [ { name: 'time', type: FieldType.time, values: TIME_VALUES_B }, { name: 'value', type: FieldType.number, values: COMPARE_VALUES }, ], meta: { timeCompare: { isTimeShiftQuery: true, diffMs: 86400000, }, }, }); const allFrames = [originalFrame, compareFrame]; expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false); }); it('should return false when compare frame refId does not end with -compare', () => { const compareFrame = toDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: TIME_VALUES_A }, { name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }, ], }); const allFrames = [compareFrame]; expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false); }); it('should return false when original frame is not found', () => { const compareFrame = toDataFrame({ refId: 'A-compare', fields: [ { name: 'time', type: FieldType.time, values: TIME_VALUES_A }, { name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }, ], }); const allFrames = [compareFrame]; // No original frame with refId 'A' expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false); }); it('should return false when compare frame has no time field', () => { const originalFrame = toDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: TIME_VALUES_A }, { name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }, ], }); const compareFrame = toDataFrame({ refId: 'A-compare', fields: [{ name: 'value', type: FieldType.number, values: COMPARE_VALUES }], }); const allFrames = [originalFrame, compareFrame]; expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false); }); it('should return false when original frame has no time field', () => { const originalFrame = toDataFrame({ refId: 'A', fields: [{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }], }); const compareFrame = toDataFrame({ refId: 'A-compare', fields: [ { name: 'time', type: FieldType.time, values: TIME_VALUES_A }, { name: 'value', type: FieldType.number, values: COMPARE_VALUES }, ], }); const allFrames = [originalFrame, compareFrame]; expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false); }); it('should return false when time fields have empty values', () => { const EMPTY_VALUES: number[] = []; const originalFrame = toDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: EMPTY_VALUES }, { name: 'value', type: FieldType.number, values: EMPTY_VALUES }, ], }); const compareFrame = toDataFrame({ refId: 'A-compare', fields: [ { name: 'time', type: FieldType.time, values: EMPTY_VALUES }, { name: 'value', type: FieldType.number, values: EMPTY_VALUES }, ], }); const allFrames = [originalFrame, compareFrame]; expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false); }); it('should handle null values and return true when first non-null time is before range', () => { const TIME_WITH_NULLS = [null, ...TIME_VALUES_A]; const ORIGINAL_WITH_NULLS = [null, ...ORIGINAL_VALUES]; const COMPARE_WITH_NULLS = [null, ...COMPARE_VALUES]; const originalFrame = toDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: TIME_WITH_NULLS }, { name: 'value', type: FieldType.number, values: ORIGINAL_WITH_NULLS }, ], }); const compareFrame = toDataFrame({ refId: 'A-compare', fields: [ { name: 'time', type: FieldType.time, values: TIME_WITH_NULLS }, { name: 'value', type: FieldType.number, values: COMPARE_WITH_NULLS }, ], }); const allFrames = [originalFrame, compareFrame]; expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(true); }); it('should return false when all time values are null', () => { const ALL_NULL_TIMES = [null, null, null]; const originalFrame = toDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: ALL_NULL_TIMES }, { name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }, ], }); const compareFrame = toDataFrame({ refId: 'A-compare', fields: [ { name: 'time', type: FieldType.time, values: ALL_NULL_TIMES }, { name: 'value', type: FieldType.number, values: COMPARE_VALUES }, ], }); const allFrames = [originalFrame, compareFrame]; expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false); }); });