Added padding and factored out the templating
This commit is contained in:
@@ -167,9 +167,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
||||
],
|
||||
"packages/grafana-data/src/transformations/transformers/calculateField.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
|
||||
@@ -15,6 +15,7 @@ import { FieldMatcherID } from '../matchers/ids';
|
||||
import { ensureColumnsTransformer } from './ensureColumns';
|
||||
import { DataTransformerID } from './ids';
|
||||
import { noopTransformer } from './noop';
|
||||
import { parseTemplateTokens, processTemplateTokens } from './utils';
|
||||
|
||||
export enum CalculateFieldMode {
|
||||
ReduceRow = 'reduceRow',
|
||||
@@ -119,6 +120,7 @@ export interface CalculateFieldTransformerOptions {
|
||||
// Output field properties
|
||||
alias?: string; // The output field name
|
||||
// TODO: config?: FieldConfig; or maybe field overrides? since the UI exists
|
||||
returnType?: FieldType;
|
||||
}
|
||||
|
||||
type ValuesCreator = (data: DataFrame) => unknown[] | undefined;
|
||||
@@ -257,30 +259,12 @@ export const calculateFieldTransformer: DataTransformerInfo<CalculateFieldTransf
|
||||
});
|
||||
case CalculateFieldMode.TemplateExpression:
|
||||
const expression = options.template?.expression ?? '';
|
||||
const exprFieldNames = [
|
||||
...new Set([...expression.matchAll(/\{([\w\d\._-]+)\}/g)].map((match) => match[1])),
|
||||
];
|
||||
type FieldData = { fieldName: string; values: any[] };
|
||||
|
||||
const parsedTemplateTokens = parseTemplateTokens(expression);
|
||||
options.returnType = FieldType.string;
|
||||
creator = (frame: DataFrame) => {
|
||||
if (!options.template) {
|
||||
return undefined;
|
||||
}
|
||||
const exprFieldValues: FieldData[] = exprFieldNames.reduce((values: FieldData[], fieldName: string) => {
|
||||
const field = frame.fields.find((f) => f.name === fieldName);
|
||||
if (field) {
|
||||
values.push({ fieldName, values: field.values });
|
||||
}
|
||||
return values;
|
||||
}, []);
|
||||
|
||||
const outValues = [];
|
||||
const outValues: string[] = [];
|
||||
for (let i = 0; i < frame.length; i++) {
|
||||
let rowValue = expression;
|
||||
exprFieldValues.map(({ fieldName, values }) => {
|
||||
rowValue = rowValue.replaceAll(`{${fieldName}}`, values[i]);
|
||||
});
|
||||
outValues.push(rowValue);
|
||||
outValues.push(processTemplateTokens(expression, parsedTemplateTokens, frame.fields, i));
|
||||
}
|
||||
return outValues;
|
||||
};
|
||||
@@ -305,7 +289,7 @@ export const calculateFieldTransformer: DataTransformerInfo<CalculateFieldTransf
|
||||
|
||||
const field: Field = {
|
||||
name: getNameFromOptions(options),
|
||||
type: FieldType.number,
|
||||
type: options.returnType ?? FieldType.number,
|
||||
config: {},
|
||||
values,
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { fieldMatchers } from '../matchers';
|
||||
import { FieldMatcherID } from '../matchers/ids';
|
||||
|
||||
import { DataTransformerID } from './ids';
|
||||
import { parseTemplateTokens, processTemplateTokens } from './utils';
|
||||
|
||||
export enum FormatStringOutput {
|
||||
UpperCase = 'Upper Case',
|
||||
@@ -64,18 +65,14 @@ export const getFormatStringFunction = (options: FormatStringTransformerOptions)
|
||||
case FormatStringOutput.Substring:
|
||||
return value.substring(options.substringStart, options.substringEnd);
|
||||
case FormatStringOutput.Affix:
|
||||
let prefix = { value: options.stringPrefix ?? '' };
|
||||
let suffix = { value: options.stringSuffix ?? '' };
|
||||
[prefix, suffix].map((affix) => {
|
||||
const matches = [...affix.value.matchAll(/\{([\w\d\._-]+)\}/g)];
|
||||
matches.forEach((match) => {
|
||||
const fieldName = match[1];
|
||||
const matchingField = allFields.find((field) => field.name === fieldName);
|
||||
if (matchingField) {
|
||||
affix.value = affix.value.replace(`{${fieldName}}`, matchingField.values[index]);
|
||||
}
|
||||
});
|
||||
const prefix = { value: options.stringPrefix ?? '' };
|
||||
const suffix = { value: options.stringSuffix ?? '' };
|
||||
|
||||
[prefix, suffix].forEach((affix) => {
|
||||
const parsedTemplateTokens = parseTemplateTokens(affix.value);
|
||||
affix.value = processTemplateTokens(affix.value, parsedTemplateTokens, allFields, index);
|
||||
});
|
||||
|
||||
return prefix.value + value + suffix.value;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BootData } from '../../types/config';
|
||||
import { DataFrame } from '../../types/dataFrame';
|
||||
import { DataFrame, Field } from '../../types/dataFrame';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -22,3 +22,57 @@ export function findMaxFields(data: DataFrame[]) {
|
||||
|
||||
return maxFields;
|
||||
}
|
||||
|
||||
export interface TemplateToken {
|
||||
token: string;
|
||||
fieldName: string;
|
||||
fillChar?: string;
|
||||
alignment?: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all template tokens from a string along with any padding modifiers.
|
||||
*/
|
||||
export function parseTemplateTokens(templateString: string): TemplateToken[] {
|
||||
const matchExpr = /{(?<fieldName>[\w\d\._-]+)(?::(?<fillChar>.?(?=[<>^]))?(?<alignment>[<>^])?(?<width>\d+))?}/g;
|
||||
return [...templateString.matchAll(matchExpr)].map((match) => ({
|
||||
token: match[0],
|
||||
fieldName: match.groups!.fieldName,
|
||||
fillChar: match.groups!.fillChar || ' ',
|
||||
alignment: match.groups!.alignment || '<',
|
||||
width: parseInt(match.groups!.width, 10),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a template string based on the extracted tokens.
|
||||
*/
|
||||
export function processTemplateTokens(
|
||||
templateString: string,
|
||||
templateTokens: TemplateToken[],
|
||||
allFields: Field[],
|
||||
index: number
|
||||
) {
|
||||
let resultString = templateString;
|
||||
templateTokens.forEach((templateToken) => {
|
||||
const { token, fieldName, fillChar, alignment, width } = templateToken;
|
||||
const matchingField = allFields.find((field) => field.name === fieldName);
|
||||
if (!matchingField) {
|
||||
return;
|
||||
}
|
||||
let replacementValue = matchingField.values[index]?.toString() ?? '';
|
||||
if (alignment === '<') {
|
||||
replacementValue = replacementValue.padEnd(width, fillChar || ' ');
|
||||
} else if (alignment === '>') {
|
||||
replacementValue = replacementValue.padStart(width, fillChar || ' ');
|
||||
} else if (alignment === '^') {
|
||||
const padding = Math.floor(Math.max(0, width - replacementValue.length) / 2);
|
||||
replacementValue = replacementValue
|
||||
.padStart(replacementValue.length + padding, fillChar || ' ')
|
||||
.padEnd(Number(width), fillChar || ' ');
|
||||
}
|
||||
resultString = resultString.replaceAll(token, replacementValue);
|
||||
});
|
||||
return resultString;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,12 @@ Use this transformation to add a new field calculated from two other fields. Eac
|
||||
- **Stddev** - Calculates the moving standard deviation.
|
||||
- **Variance** - Calculates the moving variance.
|
||||
- **Row index** - Insert a field with the row index.
|
||||
- **Template expression** - Insert a field with the value generated from a template expression. The expression can use the values of other fields in the calculation by using the {field} syntax.
|
||||
- **Template expression** - Insert a field with the value generated from a template expression. The expression can use the values of other fields in the calculation by using the {field} syntax, with optional modifiers for padding.
|
||||
- **No alignment** - Pastes the contents of 'field': {field}
|
||||
- **Left alignment** - Pastes the contents of 'field' aligned left with default padding character, padded to 10 characters: {field:<10}
|
||||
- **Right alignment** - Pastes the contents of 'field' aligned right with default padding character, padded to 10 characters: {field:>10}
|
||||
- **Center alignment** - Pastes the contents of 'field' center aligned with default padding character, padded to 10 characters: {field:^10}
|
||||
- **Custom padding character** - Pastes the contents of 'field' center aligned with an underscore padding character, padded to 10 characters: {field:_^10}
|
||||
- **Field name** - Select the names of fields you want to use in the calculation for the new field.
|
||||
- **Calculation** - If you select **Reduce row** mode, then the **Calculation** field appears. Click in the field to see a list of calculation choices you can use to create the new field. For information about available calculations, refer to [Calculation types][].
|
||||
- **Operation** - If you select **Binary operation** or **Unary operation** mode, then the **Operation** fields appear. These fields allow you to apply basic math operations on values in a single row from selected fields. You can also use numerical values for binary operations.
|
||||
@@ -516,7 +521,7 @@ Use this transformation to customize the output of a string field. This transfor
|
||||
- **Kebab case** - Formats all characters in the string in lowercase and uses dashes instead of spaces between words.
|
||||
- **Trim** - Removes all leading and trailing spaces from the string.
|
||||
- **Substring** - Returns a substring of the string, using the specified start and end positions.
|
||||
- **Affix** - Add a prefix or suffix string to the string. Allows pasting the value of other fields when provided {field} as the affix expression.
|
||||
- **Affix** - Add a prefix or suffix to the field. Allows pasting of values of other fields using {fieldName} with padding modifiers .
|
||||
|
||||
This transformation provides a convenient way to standardize and tailor the presentation of string data for better visualization and analysis.`;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user