The byName and byNames matchers fall back to matching any time field when the configured name is Time or time, so configs keep working after the dataplane rename. The fallback also kicked in when the frame already had a field with that name, so hiding `time` in Organize fields hid every time field and Filter by name could not drop the other ones. The fallback now only applies when no field in the frame has that name or display name.
252 lines
7.4 KiB
TypeScript
252 lines
7.4 KiB
TypeScript
import { getFieldDisplayName } from '../../field/fieldState';
|
|
import { stringToJsRegex } from '../../text/string';
|
|
import { type DataFrame, type Field, FieldType, TIME_SERIES_VALUE_FIELD_NAME } from '../../types/dataFrame';
|
|
import { type FieldMatcher, type FieldMatcherInfo, type FrameMatcherInfo } from '../../types/transformations';
|
|
|
|
import { FieldMatcherID, FrameMatcherID } from './ids';
|
|
|
|
export interface RegexpOrNamesMatcherOptions {
|
|
pattern?: string;
|
|
names?: string[];
|
|
variable?: string;
|
|
}
|
|
|
|
/**
|
|
* Mode to be able to toggle if the names matcher should match fields in provided
|
|
* list or all except provided names.
|
|
* @public
|
|
*/
|
|
export enum ByNamesMatcherMode {
|
|
exclude = 'exclude',
|
|
include = 'include',
|
|
}
|
|
|
|
/**
|
|
* Options to instruct the by names matcher to either match all fields in given list
|
|
* or all except the fields in the list.
|
|
* @public
|
|
*/
|
|
export interface ByNamesMatcherOptions {
|
|
mode?: ByNamesMatcherMode;
|
|
names?: string[];
|
|
readOnly?: boolean;
|
|
prefix?: string;
|
|
}
|
|
|
|
// General Field matcher
|
|
const fieldNameMatcher: FieldMatcherInfo<string> = {
|
|
id: FieldMatcherID.byName,
|
|
name: 'Field Name',
|
|
description: 'match the field name',
|
|
defaultOptions: '',
|
|
|
|
get: (name: string): FieldMatcher => {
|
|
const uniqueNames = new Set<string>([name]);
|
|
|
|
const fallback = fieldNameFallback(uniqueNames);
|
|
|
|
return (field: Field, frame: DataFrame, allFrames: DataFrame[]) => {
|
|
return (
|
|
name === field.name ||
|
|
name === getFieldDisplayName(field, frame, allFrames) ||
|
|
Boolean(fallback && fallback(field, frame, allFrames))
|
|
);
|
|
};
|
|
},
|
|
|
|
getOptionsDisplayText: (name: string) => {
|
|
return `Field name: ${name}`;
|
|
},
|
|
};
|
|
|
|
const multipleFieldNamesMatcher: FieldMatcherInfo<ByNamesMatcherOptions> = {
|
|
id: FieldMatcherID.byNames,
|
|
name: 'Field Names',
|
|
description: 'match any of the given the field names',
|
|
defaultOptions: {
|
|
mode: ByNamesMatcherMode.include,
|
|
names: [],
|
|
},
|
|
|
|
get: (options: ByNamesMatcherOptions): FieldMatcher => {
|
|
const { names, mode = ByNamesMatcherMode.include } = options;
|
|
const uniqueNames = new Set<string>(names ?? []);
|
|
|
|
const fallback = fieldNameFallback(uniqueNames);
|
|
|
|
const matcher = (field: Field, frame: DataFrame, frames: DataFrame[]) => {
|
|
return (
|
|
uniqueNames.has(field.name) ||
|
|
uniqueNames.has(getFieldDisplayName(field, frame, frames)) ||
|
|
Boolean(fallback && fallback(field, frame, frames))
|
|
);
|
|
};
|
|
|
|
if (mode === ByNamesMatcherMode.exclude) {
|
|
return (field: Field, frame: DataFrame, frames: DataFrame[]) => {
|
|
return !matcher(field, frame, frames);
|
|
};
|
|
}
|
|
return matcher;
|
|
},
|
|
|
|
getOptionsDisplayText: (options: ByNamesMatcherOptions): string => {
|
|
const { names, mode } = options;
|
|
const displayText = (names ?? []).join(', ');
|
|
if (mode === ByNamesMatcherMode.exclude) {
|
|
return `All except: ${displayText}`;
|
|
}
|
|
return `All of: ${displayText}`;
|
|
},
|
|
};
|
|
|
|
// In an effort to support migrating to a consistent data contract, the
|
|
// naming conventions need to get normalized. However, many existing setups
|
|
// exist that would no longer match names if that changes. This injects
|
|
// fallback logic when the data frame has not type version specified
|
|
function fieldNameFallback(fields: Set<string>) {
|
|
let fallback: FieldMatcher | undefined = undefined;
|
|
|
|
if (fields.has(TIME_SERIES_VALUE_FIELD_NAME)) {
|
|
fallback = (field: Field, frame: DataFrame) => {
|
|
return (
|
|
Boolean(field.labels) && // Value was reasonable when the name was set in labels or on the frame
|
|
field.labels?.__name__ === field.name
|
|
);
|
|
};
|
|
} else if (fields.has('Time') || fields.has('time')) {
|
|
const timeNames = ['Time', 'time'].filter((name) => fields.has(name));
|
|
fallback = (field: Field, frame: DataFrame, allFrames: DataFrame[]) => {
|
|
return (
|
|
frame.meta?.typeVersion == null &&
|
|
field.type === FieldType.time &&
|
|
// A field that already carries the name is matched directly, so other time fields must not be pulled in
|
|
!frame.fields.some(
|
|
(f) => timeNames.includes(f.name) || timeNames.includes(getFieldDisplayName(f, frame, allFrames))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
|
|
return fallback;
|
|
}
|
|
|
|
const regexpFieldNameMatcher: FieldMatcherInfo<string> = {
|
|
id: FieldMatcherID.byRegexp,
|
|
name: 'Field Name by Regexp',
|
|
description: 'match the field name by a given regexp pattern',
|
|
defaultOptions: '/.*/',
|
|
|
|
get: (pattern: string): FieldMatcher => {
|
|
const regexp = patternToRegex(pattern);
|
|
|
|
return (field: Field, frame: DataFrame, allFrames: DataFrame[]) => {
|
|
const displayName = getFieldDisplayName(field, frame, allFrames);
|
|
return !!regexp && regexp.test(displayName);
|
|
};
|
|
},
|
|
|
|
getOptionsDisplayText: (pattern: string): string => {
|
|
return `Field name by pattern: ${pattern}`;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Field matcher that will match all fields that exists in a
|
|
* data frame with configured refId.
|
|
* @public
|
|
*/
|
|
const fieldsInFrameMatcher: FieldMatcherInfo<string> = {
|
|
id: FieldMatcherID.byFrameRefID,
|
|
name: 'Fields by frame refId',
|
|
description: 'match all fields returned in data frame with refId.',
|
|
defaultOptions: '',
|
|
|
|
get: (refId: string): FieldMatcher => {
|
|
return (field: Field, frame: DataFrame, allFrames: DataFrame[]) => {
|
|
return frame.refId === refId;
|
|
};
|
|
},
|
|
|
|
getOptionsDisplayText: (refId: string): string => {
|
|
return `Math all fields returned by query with reference ID: ${refId}`;
|
|
},
|
|
};
|
|
|
|
const regexpOrMultipleNamesMatcher: FieldMatcherInfo<RegexpOrNamesMatcherOptions> = {
|
|
id: FieldMatcherID.byRegexpOrNames,
|
|
name: 'Field Name by Regexp or Names',
|
|
description: 'match the field name by a given regexp pattern or given names',
|
|
defaultOptions: {
|
|
pattern: '/.*/',
|
|
names: [],
|
|
},
|
|
|
|
get: (options: RegexpOrNamesMatcherOptions): FieldMatcher => {
|
|
const regexpMatcher = regexpFieldNameMatcher.get(options?.pattern || '');
|
|
const namesMatcher = multipleFieldNamesMatcher.get({
|
|
mode: ByNamesMatcherMode.include,
|
|
names: options?.names ?? [],
|
|
});
|
|
|
|
return (field: Field, frame: DataFrame, allFrames: DataFrame[]) => {
|
|
return namesMatcher(field, frame, allFrames) || regexpMatcher(field, frame, allFrames);
|
|
};
|
|
},
|
|
|
|
getOptionsDisplayText: (options: RegexpOrNamesMatcherOptions): string => {
|
|
const pattern = options?.pattern ?? '';
|
|
const names = options?.names?.join(',') ?? '';
|
|
return `Field name by pattern: ${pattern} or names: ${names}`;
|
|
},
|
|
};
|
|
|
|
const patternToRegex = (pattern?: string): RegExp | undefined => {
|
|
if (!pattern) {
|
|
return undefined;
|
|
}
|
|
|
|
try {
|
|
return stringToJsRegex(pattern);
|
|
} catch (error) {
|
|
console.error(error);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
// General Frame matcher
|
|
const frameNameMatcher: FrameMatcherInfo<string> = {
|
|
id: FrameMatcherID.byName,
|
|
name: 'Frame Name',
|
|
description: 'match the frame name',
|
|
defaultOptions: '/.*/',
|
|
|
|
get: (pattern: string) => {
|
|
const regex = stringToJsRegex(pattern);
|
|
return (frame: DataFrame) => {
|
|
return regex.test(frame.name || '');
|
|
};
|
|
},
|
|
|
|
getOptionsDisplayText: (pattern: string) => {
|
|
return `Frame name: ${pattern}`;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Registry Initialization
|
|
*/
|
|
export function getFieldNameMatchers(): FieldMatcherInfo[] {
|
|
return [
|
|
fieldNameMatcher,
|
|
regexpFieldNameMatcher,
|
|
multipleFieldNamesMatcher,
|
|
regexpOrMultipleNamesMatcher,
|
|
fieldsInFrameMatcher,
|
|
];
|
|
}
|
|
|
|
export function getFrameNameMatchers(): FrameMatcherInfo[] {
|
|
return [frameNameMatcher];
|
|
}
|