-
Notifications
You must be signed in to change notification settings - Fork 488
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 links to make values in tags or log properties clickable #223
Conversation
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
When are the evaluations performed, only when the span is expanded? I think we'll need some visual indication that the tag/value have links attached, maybe the hyperlink 🔗 symbol somewhere. |
@yurishkuro Thank you for your answer.
Yes, exactly, just before links are displayed.
There are already some icons on the right, but maybe they are not visible enough or not the right ones? |
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Codecov Report
@@ Coverage Diff @@
## master #223 +/- ##
==========================================
+ Coverage 89.21% 90.13% +0.92%
==========================================
Files 106 109 +3
Lines 2317 2464 +147
Branches 472 505 +33
==========================================
+ Hits 2067 2221 +154
+ Misses 211 205 -6
+ Partials 39 38 -1
Continue to review full report at Codecov.
|
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
const spanMarkup = markup.replace(/^<div /i, '<span ').replace(/<\/div>$/i, '</span>'); | ||
// eslint-disable-next-line react/no-danger | ||
return <span dangerouslySetInnerHTML={{ __html: spanMarkup }} />; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of manipulating the HTML seems like adding ub-inline-block
to the <div>
would work?
const jsonTable = (
// eslint-disable-next-line react/no-danger
<div className="ub-inline-block" dangerouslySetInnerHTML={{ __html: jsonMarkup(parseIfJson(row.value)) }} />
);
I think that might also need
.KeyValueTable--body > tr > td {
padding: 0.25rem 0.5rem;
vertical-align: top;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your suggestion. I have added a commit with this change.
@divdavem, Awesome! 🎉 Thank you for putting this together! :) I have a few questions but want to spend more time digesting it and should be able to follow up tomorrow. |
(as recommended by Joe Farro) Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
@tiffon Thank you. For info, I am on holidays tonight for a bit more than two weeks, so I may not be able to answer your questions soon. I am planning to add some more tests this afternoon before my holidays. |
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
There seems to be a failing test in Travis which is unrelated to my changes: https://travis-ci.org/jaegertracing/jaeger-ui/builds/400936060#L1378
Between the commit of the previous successful build (https://travis-ci.org/jaegertracing/jaeger-ui/builds/400922649) (136c149) and the commit of this failing travis build (a159459), I only added some tests and did not change any other code: divdavem/jaeger-ui@136c149...a159459 The data used in the failing test seem to be randomly generated, that's probably why such a failure suddenly happened in one build and does not happen in most builds, but, it may underline a real issue, either in the random data generation or in the code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @divdavem! 👍
Some questions and comments added inline to the code.
Regarding typing, not all of the code-base has flow typings, some older files are missing them, but we're trying to use flow whenever we add new files or heavily refactor. Can you add create flow types in src/types/link-patterns.js
and use them throughout the typed code?
Also, I think it makes sense to add the spanIndex
prop to the SpanDetailRow
, but a couple of things that crossed my mind:
- It might be nice to avoid passing the full trace (or even just it's spans) into
getLinks(...)
- It might be nice for spans to have a real reference to it's parent instead of just the ID (not sure yet about the full implications of this)
Just thinking out loud about the possibility of adding a .parent
property to Span
. Then, getLinks(...)
could be:
getLinks(span, items, itemIndex)`
What do you think?
Thank you for contributing!
|
||
const parameterRegExp = /\$\{([^{}]*)\}/; | ||
|
||
export function processTemplate(template, encodeFn) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the string interpolation, what do you think of using something like twitter-text/stringSupplant?
So, to get the parameter names, something like:
function getParamNames(str) {
const names = [];
str.replace(/#\{([^\}]+)\}/g, function (_, name) {
names.push(name);
});
return names;
}
And to apply them:
function stringSupplant(str, encodeFn, map) {
return str.replace(/#\{([^}]+)\}/g, function (_, name) {
const v = map[name];
return v ? encodeFn(v) : '';
});
}
I think this would simplify processTemplate()
and the resulting template()
function.
template: stringSupplant.bind(null, template, endcodeFn),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your suggestion is clearly more readable, even if it requires to execute the regular expression each time the template is executed (as opposed to only once, when processTemplate
is called). The difference in performance (if any) is probably negligible, so I have updated the PR to use your suggestion.
Do you have any preference between #{variable}
or ${variable}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
I'd suggest using the #
to differentiate from template literals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I have updated the PR to use #
instead of $
.
if (Array.isArray(entry)) { | ||
return arg => entry.indexOf(arg) > -1; | ||
} | ||
if (entry instanceof RegExp) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Planning ahead in preparation for the JS config file (#123) is awesome but it's tough to validate functionality that relies on or is impacted by the JS config before it's implemented. That increases the risks when switching to using the JS config. Maybe the regular expression and function variants of the test function should be kept on ice until a PR for #128 is merged?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have now commented the parts of the code that rely on #123.
|
||
export function getParameterInAncestor(name, spans, startSpanIndex) { | ||
let currentSpan = { depth: spans[startSpanIndex].depth + 1 }; | ||
for (let spanIndex = startSpanIndex; spanIndex >= 0; spanIndex--) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will not exit early when a root level ancestor is encountered, e.g.
for (...) {
...
if (currentSpan.depth === 0) {
break; // as there will be no more ancestors
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have now added this condition in the for loop to exit early when reaching a root level ancestor.
} | ||
const result = []; | ||
linkPatterns.forEach(pattern => { | ||
if (pattern.type(type) && pattern.key(item.key, item.value, type) && pattern.value(item.value)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is pattern.key(...)
getting item.value
and type
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allowed to have a function that can decide whether to display a link with a logic depending on those multiple parameters. I have now removed that possibility as it also depends on #123.
let parameterValues = {}; | ||
pattern.parameters.every(parameter => { | ||
let entry = getParameterInArray(parameter, items); | ||
if (!entry && !processTags) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe ancestors are also being searched when type === 'log', which seems to contradict your comment on the PR description?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, ancestors are also being searched when type === 'log'
, and this is on purpose.
Maybe I should update the PR description if it is ambiguous about this.
pattern.parameters.every(parameter => { | ||
let entry = getParameterInArray(parameter, items); | ||
if (!entry && !processTags) { | ||
// do not look in ancestors for process tags because the same object may appear in different places in the hierarchy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this comment, why are process tags always searched when looking through ancestors? Seems like the only process tags that are skipped are those on the current span.
Also, why is it relevant that process tags can be repeated? A process will match the first time it's encountered or it will fail every time it's tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getLinks
function caches results in the alreadyComputed
weak map.
The key used in that map is the object (with the key
and value
properties) that will be displayed as a link (if a link has to be displayed).
If that object is reused at different places in the hierarchy (which is the case for process tags, as the same process object can be reused for multiple spans), a call to getLinks
with an object that is already in the cache could get the result of a previous call to getLinks
from a different place in the hierarchy. If a process tag could use values from an ancestor, the ancestor from which the value could be found and the corresponding value could be different between the two places in the hierarchy, which would make getLinks
return an invalid value.
Another solution (instead of preventing process tags links from using values from ancestors) would be to have a cache that also stores the current span (so we would need a cache with 2 keys), but that would be more complex and I am not sure if it is actually needed to refer to ancestor values from a process tag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, having trouble following... Seems like items are only added to the cache in the link getter, at the outer level, and the cache is only checked at that level, too. So, when searching ancestors, previously calculated values that are cached are a non-issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will explain in Javascript:
// Let's say there is one tag like this:
let myProcessTag = {
key: 'myProcessInfo',
value: 'myProcessValue'
};
// The tag is used in a process:
let myProcess = {
tags: [myProcessTag]
};
// The same process reference is used in multiple spans in the trace hierarchy:
let trace = {
spans: [{
depth: 0
}, {
depth: 1,
tags: [{
key: 'myParentKey',
value: 'parentValue1'
}]
}, {
depth: 2,
process: myProcess
}, {
depth: 1,
tags: [{
key: 'myParentKey',
value: 'parentValue2'
}]
}, {
depth: 2,
process: myProcess
}]
};
// So myProcess is used in trace.spans[2], which is the child of trace.spans[1]
// and myProcess is also used in trace.spans[4], which is the child of trace.spans[3]
// Now, let's suppose that it is allowed for a link of a process tag to refer to tags from ancestors
// and someone defined the configuration like this:
JAEGER_CONFIG = {
"linkPatterns": [{
"key": "myProcessInfo",
"url": "http://example.org/?myProcessValue=#{myProcessInfo}&myParentValue=#{myParentKey}",
"text": "Where will this link go?"
}]
};
// This link on the process tag refers to a tag from an ancestor
// (which is the thing I am not allowing in the current code).
// Then, if we call computeLinks with trace.spans[2] and myProcessTag, we should get:
[{
"url": "http://example.org/?myProcessValue=myProcessValue&myParentValue=parentValue1",
"text": "Where will this link go?"
}]
// Now, if we call computeLinks with trace.spans[4] and the same myProcessTag, we should get:
[{
"url": "http://example.org/?myProcessValue=myProcessValue&myParentValue=parentValue2",
"text": "Where will this link go?"
}]
// These two values are different because the same tag is at two different places in the spans hierarchy,
// and the value for myParentKey is taken
// - in the first case from trace.spans[1].tags[0]
// - in the second case from trace.spans[3].tags[0]
// Now the getLink function returned by createGetLinks uses the tag object as a key in the cache.
// In our case, the tag object is myProcessTag.
// So if we first call the getLink function returned by createGetLinks for trace.spans[2] and myProcessTag :
result = cache.get(myProcessTag); // result is undefined
result = computeLinks(...);
// which means:
result = [{
"url": "http://example.org/?myProcessValue=myProcessValue&myParentValue=parentValue1",
"text": "Where will this link go?"
}];
cache.set(myProcessTag, result); // result is cached
// now if we call the getLink function for trace.spans[4] and myProcessTag :
result = cache.get(myProcessTag); // result is already computed
return [{
"url": "http://example.org/?myProcessValue=myProcessValue&myParentValue=parentValue1",
"text": "Where will this link go?"
}]; // now the return value is wrong for trace.spans[4]
I hope this explanation is clearer!
I do not have the requirement of having links on process tags that use values from ancestor spans, so I chose the simplest solution of not allowing it. Do you think it would be better (for consistency or some use cases that I don't know) to allow this and then to use as the key in the cache both the span and the tag object (for example, with a cache based on nested WeakMaps)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great explanation, I think I've got it... Don't look in ancestors for process tags because the same process object, and therefore tags, can be used for many spans. In this case, searching a span's ancestors could yield different results (if they weren't cached). But, as the cache is keyed on the initial, shared, process tag, all spans with that process would end up getting the same results even if they have different ancestors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly!
return result; | ||
} | ||
|
||
const linkPatterns = (getConfigValue('linkPatterns') || []).map(processLinkPattern).filter(value => !!value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be shortened to .filter(Boolean)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I have updated the PR.
@@ -16,6 +16,7 @@ | |||
|
|||
import React from 'react'; | |||
import jsonMarkup from 'json-markup'; | |||
import { Dropdown, Icon, Menu } from 'antd'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file looks great! Awesome.
(Not sure how to add a comment to a file instead of a line...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your encouraging feedback!
return ( | ||
// `i` is necessary in the key because row.key can repeat | ||
// eslint-disable-next-line react/no-array-index-key | ||
<tr key={`${row.key}-${i}`}> | ||
<td className="KeyValueTable--keyColumn">{row.key}</td> | ||
<td>{jsonTable}</td> | ||
<td>{valueMarkup}</td> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the code above is sufficiently intricate to warrant being broken down into sub-components at the top of the file, similar to CustomNavDropdown
. Something like LinkValue
and LinkValueList
where LinkValueList
would compose LinkValue
?
// Individual link
<LinkValue
href={href}
title={title}
content={jsonMarkup}
/>
// List of links
<LinkValueList
links={links}
content={jsonMarkup}
/>
What do you think?
Also, I think it would make sense for the link to always show the icon?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have taken into account this suggestion and updated the PR.
Note that the Dropdown
component in its overlay
property seems to work differently if there is a wrapper component such as LinkValueList
before the Menu
component, so I don't call LinkValueList
as a component (<LinkValueList ... />
) but directly as a function ({linkValueList(...)}
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thank you.
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
@tiffon Thank you for your review! Now that I am back from my holidays, I have updated the PR to take your remarks into account. To answer your question about the model, it is true that it would make the code a bit simpler to have a reference to the parent span from a child span. As you say, it would avoid having to pass the full trace and the index in the trace. Moreover it would allow |
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
@divdavem This looks great! Thank you for the changes, very thoughtful descriptions and explanations, and consideration of the parent reference! I will think more about parent references and dive further into the changes, Monday. Side note: This is the biggest PR in Jaeger UI from someone who doesn't work for Uber (by a landslide)... MILESTONE! Thank you! |
return ( | ||
// `i` is necessary in the key because row.key can repeat | ||
// eslint-disable-next-line react/no-array-index-key | ||
<tr key={`${row.key}-${i}`}> | ||
<td className="KeyValueTable--keyColumn">{row.key}</td> | ||
<td>{jsonTable}</td> | ||
<td>{valueMarkup}</td> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thank you.
names.push(name); | ||
return match; | ||
}); | ||
return _uniq(names); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could maybe use a Set
?
const names = new Set();
// ...
return Array.from(names);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
|
||
type ProcessedTemplate = { | ||
parameters: string[], | ||
template: (data: { [parameter: string]: any }) => string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be
template: { [string]: any } => string,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, would mixed
fit the bill instead of any
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok! I do not know flow very well. I mostly use Typescript.
} | ||
|
||
function stringSupplant(str, encodeFn: any => string, map) { | ||
return str.replace(parameterRegExp, (_, name) => encodeFn(map[name])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
encodeUriComponent
will stringify null (and undefined?) values, so maybe it makes sense to guard the encode function invocation?
encodeURIComponent(null)
// -> "null"
fn = () => null;
'abbc'.replace(/b/g, fn)
// -> "anullnullc"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. As links are not displayed if any parameter is missing, this problem only happens when tags are explicitly added with a null or undefined value. In this case, it is true that is better to guard the encode function invocation and have an empty string rather than the strange "undefined" or "null" strings. I have updated the code.
if (typeof template !== 'string') { | ||
/* | ||
|
||
// kept on ice until #123 is implemented: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
|
||
const identity = a => a; | ||
|
||
type ProcessedLinkPattern = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please move type definitions to the top of the file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok!
|
||
const parameterRegExp = /\$\{([^{}]*)\}/; | ||
|
||
export function processTemplate(template, encodeFn) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
I'd suggest using the #
to differentiate from template literals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your changes look great. I added minor comments, but did ask about the process tags vs caching issue that you mentioned.
Also, after thinking it over, seems like it makes sense to add a reference to the parent. It's semantically consistent with the existing model (as opposed to adding references to children), but more convenient than jsut having the span ID. Futher, process objects are already referenced among multiple spans.
Adding a span
property to the SpanReference
type and using a utility function to get the parent of the span shoul work.
Existing
jaeger-ui/packages/jaeger-ui/src/types/index.js
Lines 35 to 40 in 0188418
export type SpanReference = { | |
refType: 'CHILD_OF' | 'FOLLOWS_FROM', | |
spanID: string, | |
traceID: string, | |
}; |
With the parent added as an actual reference via the SpanReference
object
export type SpanReference = {
refType: 'CHILD_OF' | 'FOLLOWS_FROM',
span: Span,
spanID: string,
traceID: string,
};
A util function to make life easier when getting the parent could live in src/model/span.js
(I'm going to be removing the selectors
folder, at some point)
export function getParent(span: Span) {
// searches the span.references to find 'CHILD_OF' reference type or returns null
}
What do you think?
@@ -30,6 +30,7 @@ type AccordianKeyValuesProps = { | |||
highContrast?: boolean, | |||
isOpen: boolean, | |||
label: string, | |||
linksGetter: ?({ key: string, value: any }[], number) => { url: string, text: string }[], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use KeyValuePair
from src/types/index.js
which is the same type. (Just realized it's a mistake in existing code, too... doh!)
jaeger-ui/packages/jaeger-ui/src/types/index.js
Lines 21 to 24 in 0188418
type KeyValuePair = { | |
key: string, | |
value: any, | |
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok! Indeed, it is better to use named types. I have updated the code to use this KeyValuePair type and the newly added Link one so that it is more readable.
pattern.parameters.every(parameter => { | ||
let entry = getParameterInArray(parameter, items); | ||
if (!entry && !processTags) { | ||
// do not look in ancestors for process tags because the same object may appear in different places in the hierarchy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, having trouble following... Seems like items are only added to the cache in the link getter, at the outer level, and the cache is only checked at that level, too. So, when searching ancestors, previously calculated values that are cached are a non-issue?
const result = []; | ||
linkPatterns.forEach(pattern => { | ||
if (pattern.type(type) && pattern.key(item.key) && pattern.value(item.value)) { | ||
let parameterValues = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nicely done. A consideration from a reader's perspective, using the return value of the .every()
instead of setting parameterValues
to null might be easier to follow.
const ok = pattern.parameters.every(parameter => {
// ...
});
if (ok) {
result.push(...);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I did not think about using the return value, but that is a good idea, thank you!
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
@tiffon Thank you for your review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@divdavem – Awesome, looks great!
Re the parent span reference, one small suggestion. I'd say you can hook into the tree traversal and add the span
pointer then. When tree.walk(...)
is done to order the spans, the spanMap
is fully populated, so the referred to spans can be accessed from there.
jaeger-ui/packages/jaeger-ui/src/model/transform-trace-data.js
Lines 79 to 86 in 0188418
tree.walk((spanID, node, depth) => { | |
if (spanID === '__root__') { | |
return; | |
} | |
const span: ?SpanWithProcess = spanMap.get(spanID); | |
if (!span) { | |
return; | |
} |
I think the type def might need to have span
as ?Span
, there are cases where the parent is MIA (in our instrumentations, at least). Also, it's possible for FOLLOWS_FROM references to refer to spans in different traces.
pattern.parameters.every(parameter => { | ||
let entry = getParameterInArray(parameter, items); | ||
if (!entry && !processTags) { | ||
// do not look in ancestors for process tags because the same object may appear in different places in the hierarchy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great explanation, I think I've got it... Don't look in ancestors for process tags because the same process object, and therefore tags, can be used for many spans. In this case, searching a span's ancestors could yield different results (if they weren't cached). But, as the cache is keyed on the initial, shared, process tag, all spans with that process would end up getting the same results even if they have different ancestors?
…ional in SpanReference Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com>
@tiffon Thank you for your answer. I have pushed a commit to merge Note that I had to use a type cast to |
Looks great, thanks! Seems like the only alternative to LGTM I'm going to run this tomorrow and experiment around with it, then will stamp and merge. 👍 |
Ok! Thank you! |
@divdavem I believe it's working, as expected. One thing I noticed that I didn't expect: only the single kvp from the log item, span tag or process tag is searched on that span. If all parameters are not found on that particular key-value-pair, then the rest of the span (span tags, logs or process tags) is not searched. In the case of process link patterns, things end. In the case of Just curious about the rationale for not searching any further on that span? Is that part of your use-case? I'm going to circle back to the team and see if anyone has any reflections on this aspect of the link construction... |
@tiffon Can you show an example of what you are surprised about (with the configuration and the sample trace)? |
@divdavem Yes, absolutely. Thank you for asking. I got my wires crossed a bit, I believe the rest of the list of key-value-pairs is searched, for instance, the other kvps in a log message, but aside from that the rest of the span is not searched. For instance, if we have the following config (written in JavaScript but it would actually be in JSON): linkPatters: [{
"type": "logs",
"key": "errorType",
"url": "/find-more-logs?filterLevel=error&filterErrorType=#{errorType}&filterPhase=#{phase}&filterAuthResult=#{authenticationResult}&filterModus=#{modus}",
"text": "This links to a search on logs for errors of the same type with the same authentication result"
}] If we had the following span: {
spanID: 'abc',
depth: 0,
tags: [{
{ key: 'modus', value: 'operandi' },
}]
// etc...
logs: [
{
timestamp: 999,
fields: [
{ key: 'authenticationResult', value: 200 }
]
}
{
timestamp: 1001,
fields: [
{ key: 'error', value: true },
{ key: 'errorType', value: 'timeout' },
{ key: 'phase', value: 'startup' }
]
}
]
} I believe when the link pattern is processed it will not fill in the While Initially, I incorrectly assumed I was surprised that ancestor spans are searched but the rest of the initial span is not. If the logs of the initial span and the tags and process tags were checked, the link would be generated. I'll check-in with the team this afternoon and will report back. Thanks! |
@tiffon Thank you for this example. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@divdavem Thank you for the clarification! You're absolutely right, modus
would be found.
Awesome!
Also, we're always ready to learn more about how folks are using Jaeger. LMK if you'd have some free time to discuss what's working and what's not for you and your team.
Thanks so much for contributing! 🏆 💯
@tiffon Thank you very much for the time you spent discussing and reviewing this PR and for integrating it! I personally do not have much experience with Jaeger, but, being part of a UI team, I developed this feature for another team that uses Jaeger and needed this improvement. I will suggest them to share their feedback with you. Thank you again for welcoming so kindly external contributions! |
…aegertracing#223) * Link patterns to make values in tags, processes and logs clickable Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Fixing failing test Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Moving icons closer to the value to make them more visible Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding some tests Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Fixing misplaced "// eslint-disable-next-line react/no-danger" comment Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Using ub-inline-block instead of manipulating HTML in KeyValuesTable (as recommended by Joe Farro) Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding tests for createTestFunction Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding tests for getParameterInArray Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding tests for getParameterInAncestor Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding test for SpanDetailRow Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding test for callTemplate Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding test for computeLinks Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Changes following code review Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding tests for getLinks Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Changes following code review Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Using # instead of $ in link templates Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Adding a reference to the parent span in the child spans Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> * Merging addSpanReferences with transfromTraceData and making span optional in SpanReference Signed-off-by: David-Emmanuel Divernois <david-emmanuel.divernois@amadeus.com> Signed-off-by: vvvprabhakar <vvvprabhakar@gmail.com>
This pull request contains an implementation of the feature described in #216.
It allows to define in the configuration some patterns so that the values of keys from the
Tags
,Process
andLogs
sections become clickable links that open the configured URL.This has to be configured in the new
linkPatterns
property. For example:When this is declared in the configuration, every time the
jaeger.version
key appears in theprocess
section, the value of that key will be displayed as a clickable link, with an icon on the right to show that the value is clickable, for example:If several patterns match the same tag, clicking on the value will display a popup with all the links:
Links are opened in a new window or tab.
In the configuration of a link, there are two kinds of properties:
test properties: they determine whether the link should be displayed. These properties can contain a string or an array of strings specifying the possible values of that property that lead to the display of the link. All test properties must match for the link to be displayed. If a test property is not defined, it is considered to match.
type
(possible values:process
,logs
ortags
)key
value
template properties: they determine the url and text of the link. They are simple templates where each occurrence of
#{key}
is replaced by the value of the givenkey
.url
: each replacement in the URL is wrapped in a call toencodeURIComponent
text
: if there is only one link, the text of the link is displayed as a tooltip, if there are multiple links the text is displayed in the popup menuTemplate properties can depend on the value of the following keys:
process
, only the keys in the current process can be usedlogs
: the keys present in the logCommented extra features
Note that this PR originally also implemented the following possibilities that rely on the implementation of #123 (which allows the use of any JS expression instead of only JSON in the configuration). Those features have been commented in the code of the PR.
parameters
array and returns the url or the text of the linkFor example, once #123 is implemented and the corresponding code is uncommented, the previous example can also be configured in the following equivalent way (using functions):
Thank you in advance for your comments.