Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new formatting function "add" #2507

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions packages/jaeger-ui/src/utils/link-formatting.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,63 @@ describe('getParameterAndFormatter()', () => {
});
});

describe('add', () => {
test.each([1000, -1000])('offset: %s', offset => {
const result = getParameterAndFormatter(`startTime | add ${offset}`);
expect(result).toEqual({
parameterName: 'startTime',
formatFunction: expect.any(Function),
});
const startTime = new Date('2020-01-01').getTime() * 1000;
expect(result.formatFunction(startTime)).toEqual(startTime + offset);
});

test('Invalid value', () => {
const result = getParameterAndFormatter(`startTime | add 1000`);
expect(result.formatFunction('invalid')).toEqual('invalid');
});

test('Invalid offset', () => {
const result = getParameterAndFormatter('startTime | add invalid');
const startTime = new Date('2020-01-01').getTime() * 1000;
expect(result.formatFunction(startTime)).toEqual(startTime);
});
});

describe('Chaining formatting functions', () => {
test.each(['', ' ', ' ', ' '])(
'add and epoch_micros_to_date_iso - delimeter: %p',
spaceChars => {
const expression = ['startTime', 'add 60000000', 'epoch_micros_to_date_iso'].join(
`${spaceChars}|${spaceChars}`
);
const result = getParameterAndFormatter(expression);
expect(result).toEqual({
parameterName: 'startTime',
formatFunction: expect.any(Function),
});

const startTime = new Date('2020-01-01').getTime() * 1000; // Convert to microseconds
const expectedDate = new Date('2020-01-01T00:01:00.000Z').toISOString();
expect(result.formatFunction(startTime)).toEqual(expectedDate);
}
);

test.each([' ', ' ', ' '])(
'add and epoch_micros_to_date_iso with extra spaces between functions and arguments - delimeter: %',
spaceChars => {
const expression = [`startTime | add${spaceChars}60000000 | epoch_micros_to_date_iso`].join(
`${spaceChars}|${spaceChars}`
);
const result = getParameterAndFormatter(expression);

const startTime = new Date('2020-01-01').getTime() * 1000; // Convert to microseconds
const expectedDate = new Date('2020-01-01T00:01:00.000Z').toISOString();
expect(result.formatFunction(startTime)).toEqual(expectedDate);
}
);
});

test('No function', () => {
const result = getParameterAndFormatter('startTime');
expect(result).toEqual({
Expand Down
130 changes: 81 additions & 49 deletions packages/jaeger-ui/src/utils/link-formatting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,66 +14,98 @@

import { Trace } from '../types/trace';

function getFormatFunctions<T = Trace[keyof Trace]>(): Record<
const formatFunctions: Record<
string,
(value: T, ...args: string[]) => string | T
> {
return {
epoch_micros_to_date_iso: microsSinceEpoch => {
if (typeof microsSinceEpoch !== 'number') {
console.error('epoch_micros_to_date_iso can only operate on numbers, ignoring formatting', {
value: microsSinceEpoch,
});
return microsSinceEpoch;
}
<T extends Trace[keyof Trace]>(value: T, ...args: string[]) => T | string | number
> = {
epoch_micros_to_date_iso: microsSinceEpoch => {
if (typeof microsSinceEpoch !== 'number') {
console.error('epoch_micros_to_date_iso() can only operate on numbers, ignoring formatting', {
value: microsSinceEpoch,
});
return microsSinceEpoch;
}

return new Date(microsSinceEpoch / 1000).toISOString();
},
pad_start: (value, desiredLengthString: string, padCharacter: string) => {
if (typeof value !== 'string') {
console.error('pad_start can only operate on strings, ignoring formatting', {
value,
desiredLength: desiredLengthString,
padCharacter,
});
return value;
}
const desiredLength = parseInt(desiredLengthString, 10);
if (Number.isNaN(desiredLength)) {
console.error('pad_start needs a desired length as second argument, ignoring formatting', {
value,
desiredLength: desiredLengthString,
padCharacter,
});
}
return new Date(microsSinceEpoch / 1000).toISOString();
},
pad_start: (value, desiredLengthString: string, padCharacter: string) => {
if (typeof value !== 'string') {
console.error('pad_start() can only operate on strings, ignoring formatting', {
value,
desiredLength: desiredLengthString,
padCharacter,
});
return value;
}
const desiredLength = parseInt(desiredLengthString, 10);
if (Number.isNaN(desiredLength)) {
console.error('pad_start() needs a desired length as second argument, ignoring formatting', {
value,
desiredLength: desiredLengthString,
padCharacter,
});
}

return value.padStart(desiredLength, padCharacter);
},
};
}
return value.padStart(desiredLength, padCharacter);
},

add: (value, offsetString: string) => {
if (typeof value !== 'number') {
console.error('add() needs a numeric offset as an argument, ignoring formatting', {
value,
offsetString,
});
return value;
}

const offset = parseInt(offsetString, 10);
if (Number.isNaN(offset)) {
console.error('add() needs a valid offset in microseconds as second argument, ignoring formatting', {
value,
offsetString,
});
return value;
}

return value + offset;
},
};

export function getParameterAndFormatter<T = Trace[keyof Trace]>(
parameter: string
): {
parameterName: string;
formatFunction: ((value: T) => T | string) | null;
formatFunction: ((value: T) => T | string | number) | null;
} {
const parts = parameter.split('|').map(part => part.trim());
const parameterName = parts[0];
if (parts.length === 1) return { parameterName, formatFunction: null };
const [parameterName, ...formatStrings] = parameter.split('|').map(part => part.trim());

const [formatFunctionName, ...args] = parts[1].split(' ');
// const formatFunctions = getFormatFunctions<T>();

const formatFunctions = getFormatFunctions<T>();
const formatters = formatStrings
.map(formatString => {
const [formatFunctionName, ...args] = formatString.split(/ +/);
const formatFunction = formatFunctions[formatFunctionName] as
| ((value: T, ...args: string[]) => T | string | number)
| undefined;
if (!formatFunction) {
console.error(
'Unrecognized format function name, ignoring formatting. Other formatting functions may be applied',
yurishkuro marked this conversation as resolved.
Show resolved Hide resolved
{
parameter,
formatFunctionName,
validValues: Object.keys(formatFunctions),
}
);
return null;
}
return (val: T) => formatFunction(val, ...args);
})
.filter((fn): fn is NonNullable<typeof fn> => fn != null);

const formatFunction = formatFunctions[formatFunctionName];
if (!formatFunction) {
console.error('Unrecognized format function name, ignoring formatting', {
parameter,
formatFunctionName,
validValues: Object.keys(formatFunctions),
});
}
const chainedFormatFunction = (value: T) => formatters.reduce((acc, fn) => fn(acc) as T, value);

return { parameterName, formatFunction: formatFunction ? val => formatFunction(val, ...args) : null };
return {
parameterName,
formatFunction: formatters.length ? chainedFormatFunction : null,
};
}
Loading