mirror of https://github.com/grafana/grafana
Transform: fixes so we match the field based on the proper name. (#24659)
* fixes so we match the transformer based on name properly. * changed the signature on the FieldMatcher. * introduced a names option so you can filter in name specificly. * changed so the matcher UI uses the new options format. * moved the exported functions together. * changing editors a bit. * made the filter by name work with both regex and name filtering. * fixed failing tests and make sure we always parse regex the same way. * removed unused code. * simplified to make the existing field overrides still working. * fixed issue reported by hugo. * added tests for the name matcher. * added tests for filter by name. * added more tests.pull/24708/head^2
parent
0e8638ec92
commit
96f26cbd5b
@ -0,0 +1,136 @@ |
||||
import { DataTransformerID } from './ids'; |
||||
import { toDataFrame } from '../../dataframe/processDataFrame'; |
||||
import { FieldType } from '../../types/dataFrame'; |
||||
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; |
||||
import { transformDataFrame } from '../transformDataFrame'; |
||||
import { ensureColumnsTransformer } from './ensureColumns'; |
||||
import { seriesToColumnsTransformer } from './seriesToColumns'; |
||||
|
||||
const seriesA = toDataFrame({ |
||||
fields: [ |
||||
{ name: 'TheTime', type: FieldType.time, values: [1000, 2000] }, |
||||
{ name: 'A', type: FieldType.number, values: [1, 100] }, |
||||
], |
||||
}); |
||||
|
||||
const seriesBC = toDataFrame({ |
||||
fields: [ |
||||
{ name: 'TheTime', type: FieldType.time, values: [1000, 2000] }, |
||||
{ name: 'B', type: FieldType.number, values: [2, 200] }, |
||||
{ name: 'C', type: FieldType.number, values: [3, 300] }, |
||||
{ name: 'D', type: FieldType.string, values: ['first', 'second'] }, |
||||
], |
||||
}); |
||||
|
||||
const seriesNoTime = toDataFrame({ |
||||
fields: [ |
||||
{ name: 'B', type: FieldType.number, values: [2, 200] }, |
||||
{ name: 'C', type: FieldType.number, values: [3, 300] }, |
||||
{ name: 'D', type: FieldType.string, values: ['first', 'second'] }, |
||||
], |
||||
}); |
||||
|
||||
describe('ensureColumns transformer', () => { |
||||
beforeAll(() => { |
||||
mockTransformationsRegistry([ensureColumnsTransformer, seriesToColumnsTransformer]); |
||||
}); |
||||
|
||||
it('will transform to columns if time field exists and multiple frames', () => { |
||||
const cfg = { |
||||
id: DataTransformerID.ensureColumns, |
||||
options: {}, |
||||
}; |
||||
|
||||
const data = [seriesA, seriesBC]; |
||||
const filtered = transformDataFrame([cfg], data); |
||||
|
||||
expect(filtered.length).toEqual(1); |
||||
expect(filtered[0]).toMatchInlineSnapshot(` |
||||
Object { |
||||
"fields": Array [ |
||||
Object { |
||||
"config": Object {}, |
||||
"labels": undefined, |
||||
"name": "TheTime", |
||||
"type": "time", |
||||
"values": Array [ |
||||
1000, |
||||
2000, |
||||
], |
||||
}, |
||||
Object { |
||||
"config": Object {}, |
||||
"labels": Object {}, |
||||
"name": "A", |
||||
"type": "number", |
||||
"values": Array [ |
||||
1, |
||||
100, |
||||
], |
||||
}, |
||||
Object { |
||||
"config": Object {}, |
||||
"labels": Object {}, |
||||
"name": "B", |
||||
"type": "number", |
||||
"values": Array [ |
||||
2, |
||||
200, |
||||
], |
||||
}, |
||||
Object { |
||||
"config": Object {}, |
||||
"labels": Object {}, |
||||
"name": "C", |
||||
"type": "number", |
||||
"values": Array [ |
||||
3, |
||||
300, |
||||
], |
||||
}, |
||||
Object { |
||||
"config": Object {}, |
||||
"labels": Object {}, |
||||
"name": "D", |
||||
"type": "string", |
||||
"values": Array [ |
||||
"first", |
||||
"second", |
||||
], |
||||
}, |
||||
], |
||||
"meta": Object { |
||||
"transformations": Array [ |
||||
"ensureColumns", |
||||
], |
||||
}, |
||||
"name": undefined, |
||||
"refId": undefined, |
||||
} |
||||
`);
|
||||
}); |
||||
|
||||
it('will not transform to columns if time field is missing for any of the series', () => { |
||||
const cfg = { |
||||
id: DataTransformerID.ensureColumns, |
||||
options: {}, |
||||
}; |
||||
|
||||
const data = [seriesBC, seriesNoTime]; |
||||
const filtered = transformDataFrame([cfg], data); |
||||
|
||||
expect(filtered).toEqual(data); |
||||
}); |
||||
|
||||
it('will not transform to columns if only one series', () => { |
||||
const cfg = { |
||||
id: DataTransformerID.ensureColumns, |
||||
options: {}, |
||||
}; |
||||
|
||||
const data = [seriesBC]; |
||||
const filtered = transformDataFrame([cfg], data); |
||||
|
||||
expect(filtered).toEqual(data); |
||||
}); |
||||
}); |
@ -0,0 +1,43 @@ |
||||
import { seriesToColumnsTransformer } from './seriesToColumns'; |
||||
import { DataFrame } from '../../types/dataFrame'; |
||||
import { getTimeField } from '../../dataframe/processDataFrame'; |
||||
import { DataTransformerInfo } from '../../types/transformations'; |
||||
import { DataTransformerID } from './ids'; |
||||
|
||||
export const ensureColumnsTransformer: DataTransformerInfo = { |
||||
id: DataTransformerID.ensureColumns, |
||||
name: 'Ensure Columns Transformer', |
||||
description: 'Will check if current data frames is series or columns. If in series it will convert to columns.', |
||||
transformer: () => (data: DataFrame[]) => { |
||||
// Assume timeseries should first be joined by time
|
||||
const timeFieldName = findConsistentTimeFieldName(data); |
||||
|
||||
if (data.length > 1 && timeFieldName) { |
||||
return seriesToColumnsTransformer.transformer({ |
||||
byField: timeFieldName, |
||||
})(data); |
||||
} |
||||
|
||||
return data; |
||||
}, |
||||
}; |
||||
|
||||
/** |
||||
* Find the name for the time field used in all frames (if one exists) |
||||
*/ |
||||
function findConsistentTimeFieldName(data: DataFrame[]): string | undefined { |
||||
let name: string | undefined = undefined; |
||||
for (const frame of data) { |
||||
const { timeField } = getTimeField(frame); |
||||
if (!timeField) { |
||||
return undefined; // Not timeseries
|
||||
} |
||||
if (!name) { |
||||
name = timeField.name; |
||||
} else if (name !== timeField.name) { |
||||
// Second frame has a different time column?!
|
||||
return undefined; |
||||
} |
||||
} |
||||
return name; |
||||
} |
@ -1,18 +1,18 @@ |
||||
export enum DataTransformerID { |
||||
// join = 'join', // Pick a field and merge all series based on that field
|
||||
append = 'append', // Merge all series together
|
||||
append = 'append', |
||||
// rotate = 'rotate', // Columns to rows
|
||||
reduce = 'reduce', // Run calculations on fields
|
||||
order = 'order', // order fields based on user configuration
|
||||
organize = 'organize', // order, rename and filter based on user configuration
|
||||
rename = 'rename', // rename field based on user configuration
|
||||
calculateField = 'calculateField', // Run a reducer on the row
|
||||
|
||||
seriesToColumns = 'seriesToColumns', // former table transform timeseries_to_columns
|
||||
labelsToFields = 'labelsToFields', // former table transform table
|
||||
filterFields = 'filterFields', // Pick some fields (keep all frames)
|
||||
filterFieldsByName = 'filterFieldsByName', // Pick fields with name matching regex (keep all frames)
|
||||
filterFrames = 'filterFrames', // Pick some frames (keep all fields)
|
||||
filterByRefId = 'filterByRefId', // Pick some frames by RefId
|
||||
noop = 'noop', // Does nothing to the dataframe
|
||||
reduce = 'reduce', |
||||
order = 'order', |
||||
organize = 'organize', |
||||
rename = 'rename', |
||||
calculateField = 'calculateField', |
||||
seriesToColumns = 'seriesToColumns', |
||||
labelsToFields = 'labelsToFields', |
||||
filterFields = 'filterFields', |
||||
filterFieldsByName = 'filterFieldsByName', |
||||
filterFrames = 'filterFrames', |
||||
filterByRefId = 'filterByRefId', |
||||
noop = 'noop', |
||||
ensureColumns = 'ensureColumns', |
||||
} |
||||
|
Loading…
Reference in new issue