Files
grafana-grafana/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts
T
dualfroz c09f0db570 Transformations: Match only the field spelled time when Time is configured
When the configured name is Time and the frame has a field named time
(or the other way round), the fallback still matched every time field.
Now it only matches the field with the other spelling, and keeps
matching any time field when no field goes by either name.
2026-10-18 07:17:46 +02:00

524 lines
15 KiB
TypeScript

import { toDataFrame } from '../../dataframe/processDataFrame';
import { type DataFrame, FieldType } from '../../types/dataFrame';
import { getFieldMatcher } from '../matchers';
import { FieldMatcherID } from './ids';
import { ByNamesMatcherMode } from './nameMatcher';
describe('Field Name by Regexp Matcher', () => {
it('Match all with wildcard regex', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'A hello world' }, { name: 'AAA' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byRegexp,
options: '/.*/',
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(true);
}
});
it('Match all with decimals regex', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: '12' }, { name: '112' }, { name: '13' }],
});
const config = {
id: FieldMatcherID.byRegexp,
options: '/^\\d+$/',
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(true);
}
});
it('Match complex regex', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'some.instance.path' }, { name: '112' }, { name: '13' }],
});
const config = {
id: FieldMatcherID.byRegexp,
options: '/\\b(?:\\S+?\\.)+\\S+\\b$/',
};
const matcher = getFieldMatcher(config);
let resultCount = 0;
for (const field of seriesWithNames.fields) {
if (matcher(field, seriesWithNames, [seriesWithNames])) {
resultCount++;
}
expect(resultCount).toBe(1);
}
});
});
describe('Field Name Matcher', () => {
it('Match only exact name', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'A hello world' }, { name: 'AAA' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byName,
options: 'C',
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
const didMatch = matcher(field, seriesWithNames, [seriesWithNames]);
expect(didMatch).toBe(field.name === 'C');
}
});
it('Match should respect letter case', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: '12' }, { name: '112' }, { name: '13' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byName,
options: 'c',
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(false);
}
});
it('Match name or displayName', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'a', config: { displayName: 'LongerName' } }],
});
const field = seriesWithNames.fields[0];
expect(
getFieldMatcher({
id: FieldMatcherID.byName,
options: 'c',
})(field, seriesWithNames, [seriesWithNames])
).toBe(false); // No match
expect(
getFieldMatcher({
id: FieldMatcherID.byName,
options: 'a',
})(field, seriesWithNames, [seriesWithNames])
).toBe(true);
expect(
getFieldMatcher({
id: FieldMatcherID.byName,
options: 'LongerName',
})(field, seriesWithNames, [seriesWithNames])
).toBe(true);
});
it('Match none of the field names', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'some.instance.path' }, { name: '112' }, { name: '13' }],
});
const config = {
id: FieldMatcherID.byName,
options: '',
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(false);
}
});
});
describe('Field Multiple Names Matcher', () => {
it('Match only exact name', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'A hello world' }, { name: 'AAA' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byNames,
options: {
mode: ByNamesMatcherMode.include,
names: ['C'],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
const didMatch = matcher(field, seriesWithNames, [seriesWithNames]);
expect(didMatch).toBe(field.name === 'C');
}
});
it('Match should default to include mode', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'A hello world' }, { name: 'AAA' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byNames,
options: {
names: ['C'],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
const didMatch = matcher(field, seriesWithNames, [seriesWithNames]);
expect(didMatch).toBe(field.name === 'C');
}
});
it('Match should respect letter case', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: '12' }, { name: '112' }, { name: '13' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byNames,
options: {
mode: ByNamesMatcherMode.include,
names: ['c'],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(false);
}
});
it('Match none of the field names', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'some.instance.path' }, { name: '112' }, { name: '13' }],
});
const config = {
id: FieldMatcherID.byNames,
options: {
mode: ByNamesMatcherMode.include,
names: [],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(false);
}
});
it('Match all of the field names', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'some.instance.path' }, { name: '112' }, { name: '13' }],
});
const config = {
id: FieldMatcherID.byNames,
options: {
mode: ByNamesMatcherMode.include,
names: ['some.instance.path', '112', '13'],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(true);
}
});
it('Match all but supplied names', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'A hello world' }, { name: 'AAA' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byNames,
options: {
mode: ByNamesMatcherMode.exclude,
names: ['C'],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
const didMatch = matcher(field, seriesWithNames, [seriesWithNames]);
expect(didMatch).toBe(field.name !== 'C');
}
});
});
describe('Field Regexp or Names Matcher', () => {
it('Match only exact name by name', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'A hello world' }, { name: 'AAA' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byRegexpOrNames,
options: {
names: ['C'],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
const didMatch = matcher(field, seriesWithNames, [seriesWithNames]);
expect(didMatch).toBe(field.name === 'C');
}
});
it('Match all starting with AA', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'A hello world' }, { name: 'AAA' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byRegexpOrNames,
options: {
pattern: '/^AA/',
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
const didMatch = matcher(field, seriesWithNames, [seriesWithNames]);
expect(didMatch).toBe(field.name === 'AAA');
}
});
it('Match all starting with AA and C', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'A hello world' }, { name: 'AAA' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byRegexpOrNames,
options: {
pattern: '/^AA/',
names: ['C'],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
const didMatch = matcher(field, seriesWithNames, [seriesWithNames]);
expect(didMatch).toBe(field.name === 'AAA' || field.name === 'C');
}
});
it('Match should respect letter case by name if not igored in pattern', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: '12' }, { name: '112' }, { name: '13' }, { name: 'C' }],
});
const config = {
id: FieldMatcherID.byRegexpOrNames,
options: {
names: ['c'],
pattern: '/c/i',
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
const didMatch = matcher(field, seriesWithNames, [seriesWithNames]);
expect(didMatch).toBe(field.name === 'C');
}
});
it('Match none of the field names by name', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'some.instance.path' }, { name: '112' }, { name: '13' }],
});
const config = {
id: FieldMatcherID.byRegexpOrNames,
options: {
names: [],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(false);
}
});
it('Match all of the field names by name', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'some.instance.path' }, { name: '112' }, { name: '13' }],
});
const config = {
id: FieldMatcherID.byRegexpOrNames,
options: {
names: ['some.instance.path', '112', '13'],
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(true);
}
});
it('Match all of the field names by regexp', () => {
const seriesWithNames = toDataFrame({
fields: [{ name: 'some.instance.path' }, { name: '112' }, { name: '13' }],
});
const config = {
id: FieldMatcherID.byRegexpOrNames,
options: {
pattern: '/.*/',
},
};
const matcher = getFieldMatcher(config);
for (const field of seriesWithNames.fields) {
expect(matcher(field, seriesWithNames, [seriesWithNames])).toBe(true);
}
});
it('Support fallback name matchers', () => {
const frame: DataFrame = {
fields: [
{ name: 'time', type: FieldType.time, config: {}, values: [1, 2] },
{
name: 'UP',
type: FieldType.number,
config: {},
values: [1, 2],
labels: { __name__: 'UP' },
},
],
name: 'X',
length: 2,
};
let matcher = getFieldMatcher({
id: FieldMatcherID.byName,
options: 'Value',
});
expect(matcher(frame.fields[0], frame, [])).toBeFalsy();
expect(matcher(frame.fields[1], frame, [])).toBeTruthy();
matcher = getFieldMatcher({
id: FieldMatcherID.byName,
options: 'Time',
});
expect(matcher(frame.fields[0], frame, [])).toBeTruthy();
expect(matcher(frame.fields[1], frame, [])).toBeFalsy();
});
});
it('Support fallback multiple names matchers', () => {
const frame: DataFrame = {
fields: [
{ name: 'time', type: FieldType.time, config: {}, values: [1, 2] },
{
name: 'UP',
type: FieldType.number,
config: {},
values: [1, 2],
labels: { __name__: 'UP' },
},
],
name: 'X',
length: 2,
};
let matcher = getFieldMatcher({
id: FieldMatcherID.byNames,
options: {
mode: ByNamesMatcherMode.include,
names: ['Value'],
},
});
expect(matcher(frame.fields[0], frame, [])).toBeFalsy();
expect(matcher(frame.fields[1], frame, [])).toBeTruthy();
matcher = getFieldMatcher({
id: FieldMatcherID.byNames,
options: {
mode: ByNamesMatcherMode.include,
names: ['Time'],
},
});
expect(matcher(frame.fields[0], frame, [])).toBeTruthy();
expect(matcher(frame.fields[1], frame, [])).toBeFalsy();
});
it('Should only match the field named time when the frame has more time fields', () => {
const frame: DataFrame = {
fields: [
{ name: 'time', type: FieldType.time, config: {}, values: [1, 2] },
{ name: 'Shifted Time', type: FieldType.time, config: {}, values: [3, 4] },
{ name: 'A-series', type: FieldType.number, config: {}, values: [1, 2] },
],
length: 2,
};
const nameMatcher = getFieldMatcher({
id: FieldMatcherID.byName,
options: 'time',
});
expect(nameMatcher(frame.fields[0], frame, [frame])).toBeTruthy();
expect(nameMatcher(frame.fields[1], frame, [frame])).toBeFalsy();
expect(nameMatcher(frame.fields[2], frame, [frame])).toBeFalsy();
const namesMatcher = getFieldMatcher({
id: FieldMatcherID.byNames,
options: {
mode: ByNamesMatcherMode.include,
names: ['time', 'A-series'],
},
});
expect(namesMatcher(frame.fields[0], frame, [frame])).toBeTruthy();
expect(namesMatcher(frame.fields[1], frame, [frame])).toBeFalsy();
expect(namesMatcher(frame.fields[2], frame, [frame])).toBeTruthy();
const aliasMatcher = getFieldMatcher({
id: FieldMatcherID.byName,
options: 'Time',
});
expect(aliasMatcher(frame.fields[0], frame, [frame])).toBeTruthy();
expect(aliasMatcher(frame.fields[1], frame, [frame])).toBeFalsy();
expect(aliasMatcher(frame.fields[2], frame, [frame])).toBeFalsy();
});
describe('Fields returned by query with refId', () => {
it('Match all fields in frame with refId: A', () => {
const data = [
toDataFrame({
refId: 'A',
fields: [{ name: 'field_1' }, { name: 'field_2' }],
}),
toDataFrame({
refId: 'B',
fields: [{ name: 'field_1' }, { name: 'field_2' }],
}),
];
const matcher = getFieldMatcher({
id: FieldMatcherID.byFrameRefID,
options: 'A',
});
const frameA = data[0];
expect(matcher(frameA.fields[0], frameA, data)).toBe(true);
expect(matcher(frameA.fields[1], frameA, data)).toBe(true);
const frameB = data[1];
expect(matcher(frameB.fields[0], frameB, data)).toBe(false);
expect(matcher(frameB.fields[1], frameB, data)).toBe(false);
});
});