Skip to content

Commit

Permalink
Allow formatting link parameter values as ISO date #2487 (#2501)
Browse files Browse the repository at this point in the history
## Which problem is this PR solving?
Resolves #2487

## Description of the changes
Add the ability to format link variables.

## How was this change tested?
Unit tests and by running it locally with the following UI config
```javascript
const DEFAULT_CONFIG = {
  "linkPatterns": [
    {
      "type": "traces",
      "url": "https://myOpenSearch.com/_dashboards/app/discover#/?_g=(time:(from:'#{startTime | epoch_micros_to_date_iso}',to:'#{endTime | epoch_micros_to_date_iso}'))&_a=(index:filebeat-all,query:(language:kuery,query:'#{traceID}'))",
      "text": "Logs"
    }
  ]
};
```

## Checklist
- [x] I have read
https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md
- [x] I have signed all commits
- [x] I have added unit tests for the new functionality
- [x] I have run lint and test steps successfully
  - for `jaeger`: `make lint test`
  - for `jaeger-ui`: `yarn lint` and `yarn test`

Signed-off-by: Drew Corlin <drew.corlin@getgarner.com>
  • Loading branch information
drewcorlin1 authored Nov 30, 2024
1 parent a82a96c commit d9315c6
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 7 deletions.
15 changes: 12 additions & 3 deletions packages/jaeger-ui/src/model/link-patterns.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,11 @@ describe('computeTraceLink()', () => {
url: 'http://example.com/?traceID=#{traceID}&traceName=#{traceName}&startTime=#{startTime}&endTime=#{endTime}&duration=#{duration}',
text: 'third link (#{traceID}, #{traceName}, #{startTime}, #{endTime}, #{duration})',
},
{
type: 'traces',
url: 'http://example.com/?startTime=#{startTime | epoch_micros_to_date_iso}&endTime=#{endTime | epoch_micros_to_date_iso}',
text: 'third link (#{startTime | epoch_micros_to_date_iso}, #{endTime | epoch_micros_to_date_iso})',
},
].map(processLinkPattern);

const trace = {
Expand All @@ -357,7 +362,7 @@ describe('computeTraceLink()', () => {
traceID: 'trc1',
spans: [],
startTime: 1000,
endTime: 3000,
endTime: 3000000000000,
duration: 2000,
services: [],
};
Expand All @@ -369,8 +374,12 @@ describe('computeTraceLink()', () => {
text: 'first link (trc1)',
},
{
url: 'http://example.com/?traceID=trc1&traceName=theTrace&startTime=1000&endTime=3000&duration=2000',
text: 'third link (trc1, theTrace, 1000, 3000, 2000)',
url: 'http://example.com/?traceID=trc1&traceName=theTrace&startTime=1000&endTime=3000000000000&duration=2000',
text: 'third link (trc1, theTrace, 1000, 3000000000000, 2000)',
},
{
text: 'third link (1970-01-01T00:00:00.001Z, 1970-02-04T17:20:00.000Z)',
url: 'http://example.com/?startTime=1970-01-01T00%3A00%3A00.001Z&endTime=1970-02-04T17%3A20%3A00.000Z',
},
]);
});
Expand Down
9 changes: 7 additions & 2 deletions packages/jaeger-ui/src/model/link-patterns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import memoize from 'lru-memoize';

import { getConfigValue } from '../utils/config/get-config';
import { encodedStringSupplant, getParamNames } from '../utils/stringSupplant';
import { getParameterAndFormatter } from '../utils/link-formatting';
import { getParent } from './span';
import { TNil } from '../types';
import { Span, Link, KeyValuePair, Trace } from '../types/trace';
Expand Down Expand Up @@ -146,15 +147,19 @@ export function computeTraceLink(linkPatterns: ProcessedLinkPattern[], trace: Tr
.forEach(pattern => {
const parameterValues: Record<string, any> = {};
const allParameters = pattern.parameters.every(parameter => {
const traceKV = getParameterInTrace(parameter, trace);
const { parameterName, formatFunction } = getParameterAndFormatter(parameter);
const traceKV = getParameterInTrace(parameterName, trace);

if (traceKV) {
// At this point is safe to access to trace object using parameter variable because
// we validated parameter against validKeys, this implies that parameter a keyof Trace.
parameterValues[parameter] = traceKV.value;
parameterValues[parameterName] = formatFunction ? formatFunction(traceKV.value) : traceKV.value;

return true;
}
return false;
});

if (allParameters) {
result.push({
url: callTemplate(pattern.url, parameterValues),
Expand Down
45 changes: 45 additions & 0 deletions packages/jaeger-ui/src/utils/link-formatting.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { getParameterAndFormatter } from './link-formatting';

describe('getParameterAndFormatter()', () => {
test('epoch_micros_to_date_iso', () => {
const result = getParameterAndFormatter('startTime | epoch_micros_to_date_iso');
expect(result).toEqual({
parameterName: 'startTime',
formatFunction: expect.any(Function),
});

expect(result.formatFunction(new Date('2020-01-01').getTime() * 1000)).toEqual(
'2020-01-01T00:00:00.000Z'
);
});

test('No function', () => {
const result = getParameterAndFormatter('startTime');
expect(result).toEqual({
parameterName: 'startTime',
formatFunction: null,
});
});

test('Invalid function', () => {
const result = getParameterAndFormatter('startTime | invalid');
expect(result).toEqual({
parameterName: 'startTime',
formatFunction: null,
});
});
});
52 changes: 52 additions & 0 deletions packages/jaeger-ui/src/utils/link-formatting.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2020 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

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

function getFormatFunctions<T = Trace[keyof Trace]>(): Record<string, (value: T) => 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;
}

return new Date(microsSinceEpoch / 1000).toISOString();
},
};
}

export function getParameterAndFormatter<T = Trace[keyof Trace]>(
parameter: string
): {
parameterName: string;
formatFunction: ((value: T) => T | string) | null;
} {
const [parameterName, formatFunctionName] = parameter.split('|').map(part => part.trim());
if (!formatFunctionName) return { parameterName, formatFunction: null };
const formatFunctions = getFormatFunctions<T>();

const formatFunction = formatFunctions[formatFunctionName];
if (!formatFunction) {
console.error('Unrecognized format function name, ignoring formatting', {
parameter,
formatFunctionName,
validValues: Object.keys(formatFunctions),
});
}

return { parameterName, formatFunction: formatFunction ?? null };
}
9 changes: 7 additions & 2 deletions packages/jaeger-ui/src/utils/stringSupplant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { getParameterAndFormatter } from './link-formatting';

const PARAMETER_REG_EXP = /#\{([^{}]*)\}/g;

export function encodedStringSupplant(
Expand All @@ -20,8 +22,11 @@ export function encodedStringSupplant(
map: Record<string, string | number | undefined>
) {
return str.replace(PARAMETER_REG_EXP, (_, name) => {
const mapValue = map[name];
const value = mapValue != null && encodeFn ? encodeFn(mapValue) : mapValue;
const { parameterName, formatFunction } = getParameterAndFormatter<string | number | undefined>(name);
const mapValue = map[parameterName];
const formattedValue = formatFunction && mapValue ? formatFunction(mapValue) : mapValue;

const value = formattedValue != null && encodeFn ? encodeFn(formattedValue) : mapValue;
return value == null ? '' : `${value}`;
});
}
Expand Down

0 comments on commit d9315c6

Please sign in to comment.