-
Notifications
You must be signed in to change notification settings - Fork 66
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
DOM event plugin add data-rum-id attribute #163
Conversation
return firstMatchingElement.getAttribute('data-rum-id'); | ||
} | ||
} | ||
return ''; |
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 we return
null
so that we only add theelementRumId
data to theeventData
if an element with thedata-rum-id
attribute was found? - Could I know more about your use case? This implementation would require us to update the dom event type schema to add an additional field.
- What are the performance implications if we were to look for all elements with the
data-rum-id
attribute for each element identified by the providedcssLocator
? Would this require looking through the entire DOM until one/the closest one is found?
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.
Hello Grace, thank you for your time reviewing this.
-
There is a validation for this on line 133, this would return falsy for empty string and would not add elementRumId.
if (elementRumId) {
-
I would like to capture user clicks accurately on my website which uses React on the front end. Many of the UI libraries creates child elements while rendering the page. And as a developer I don't have access to these child elements. I am unable to provide an id or a css selector. When a users clicks on a button, I either get "SPAN" or a random id that was generated during the render instead of the id / cssLocator that I provided to the Button component. This is a common issue with the current structure. This PR would solve this problem.
-
This is a great call out. I would not expect a significant impact on the performance. event.composedPath() returns an array that lists parents of the clicked element vertically. However we can add a limit for this, let's say 5 levels above or something like that. Let me know what you think, please.
@@ -129,13 +129,30 @@ export class DomEventPlugin implements Plugin { | |||
}; | |||
if (cssLocator !== undefined) { | |||
eventData.cssLocator = cssLocator; | |||
const elementRumId = this.getElementRumId(event, cssLocator); | |||
if (elementRumId) { | |||
eventData.elementRumId = elementRumId; |
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.
issue: We'll need to add elementRumId
(or simply rumId
) to the dom-event.json
schema, or it won't be ingested.
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.
Right now we have data from two different sources going into the elementId
field. I wonder if we should disambiguate these by (1) making elementId
optional and only add it if an was found, and (2) adding an element
field for the type of element.
The event would look like:
{
event: 'click',
element: 'SPAN',
cssLocator: 'button',
rumId: 'button-12345'
}
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 is a good suggestion. I agree to both.
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.
issue: We'll need to add
elementRumId
(or simplyrumId
) to thedom-event.json
schema, or it won't be ingested.
It is added to that file. And I will rename it as rumId, it looks simpler and better.
@@ -129,13 +129,30 @@ export class DomEventPlugin implements Plugin { | |||
}; | |||
if (cssLocator !== undefined) { | |||
eventData.cssLocator = cssLocator; | |||
const elementRumId = this.getElementRumId(event, cssLocator); |
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.
question: Why restrict this to css locators? For example, if I record all clicks on the document
element, I may still want to use data-rum-id
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.
Generalizing this would be even more preferable. In this case we could look up for the data-rum-id
attribute to locate the firstMatchingElement
.
Overall I think this is a good solution. One downside is that web application maintainers must an additional field Before we merge this I think we should still do some due diligence to determine:
|
|
Hey Quinn,
Note: Unit and integrations tests are failing due to the |
Thanks @omerfarukaslan !
So this would be a function that, given an Event object, returns an ID? I like this solution because it offloads the responsibility to the application, so it is a general solution, and we could continue to use a single 'elementId' field. The tradeoff is that it would require additional configuration by the application. This can be mitigated by:
Specifically, I'm thinking we could add this ID recorder to the DomEventPluginConfig |
I was thinking something like a method that users manually call from their JS to record user events. Let's say I have a function, I would like to log a user event whenever it's executed. We could let them use a method like But for now, I really liked the idea of logging the Element, ElementId and RumId properties. The only thing we could do on top of this is allowing users to enable/disable this feature from RUM config. Just like they do with the mutationObserver. What do you think? |
Thank you for your valued feedback. I added a configuration for |
Thanks @omerfarukaslan; we have a few good options here, but I'm not sure which is best. I'd like to look into this use case a bit more before committing. I think accepting a callback function which generates an ID will provide the most flexibility (users can make this function whatever they want) and safety (we won't do ancestor search by default). The question in my mind is, should we create new fields, or leave a single ID field. The tradeoff is that if we split the fields, it will be difficult to identify the correct 'id', but if we use a single field, we might loose information. Maybe we can do both somehow. |
I think using a single field is not really useful. Value type should be consistent. Currently is shows the node name for some records and element id for others. Using separate fields like element, elementId (optional) and rumId (optional) sounds like a better idea. It would make it easier to analyze the logs knowing what to expect from a certain field.
We can certainly have users define & use their own functions through config. However when we try to extract some unique id from an Event, there are not many options that users could benefit from (other than nodeName, elementId or ancestor search). RUM is currently pretty straight forward to setup and use. I'm worrying about complicating it. Please let me know what you think. |
Good point, makes sense, I think I'm on board with this. The only issue is that we're breaking backwards compatibility if we move the element type to its own field. However, I think the risk is low, and that it is worth doing now.
Even when searching ancestors, there are several ways I might do it. For example, I might want to use a different attribute name (i.e., other than I feel like accepting a function as a parameter, rather than hard-coding an ancestor search, also reduces the complexity from the perspective of the web client. It offloads the implementation to the application, so (1) if the application doesn't use it, they don't get the code, and (2) we don't need to maintain this when application owners need changes to it. |
This is a valid point and I agree. That looks like a more flexible option. I added a callback function to the config named customIdTracker. And we can provide the rumId tracker as an example in the docs. Please let me know what you think.
|
enableMutationObserver?: boolean; | ||
events: TargetDomEvent[]; | ||
}; | ||
|
||
const defaultConfig: DomEventPluginConfig = { | ||
customIdTracker: undefined, |
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.
suggestion: For all config options, we define sane default. Define a default function here and have it return undefined
. Then we can get rid of the if
statement at line 140.
customIdTracker: undefined, | |
customIdTracker: () => undefined, |
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.
Sounds like a better approach. Will add this.
enableMutationObserver?: boolean; | ||
events?: TargetDomEvent[]; | ||
}; | ||
|
||
export type DomEventPluginConfig = { | ||
customIdTracker?: (event: Event) => 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.
customIdTracker?: (event: Event) => string; | |
customIdTracker: (event: Event) => 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.
issue: We'll need to think of a good name for this. Maybe just customId
? It violates verb-phrase method name convention, but will clearly connect to the customId
property in the event schema.
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 renamed this as getCustomId
We'll also need tests before merging this PR. Unit tests are required. Integ tests are optional; i.e., if there are integrations we want to check. |
I created unit tests but I'm unable to commit them. I see this lint error when I try to commit: |
This might have been fixed by #165 ... is this still happening after merging? |
It worked after merging. Thank you! Your feedback is addressed. And I was able to add unit tests for the getCustomId() functionality. |
I've polished the change a bit. Most notably, I've renamed both the config option and the event field name Before we merge the change, we will need to (1) update the schema in the back end, and (2) add documentation for this option. @limhjgrace do these changes look ok to you? |
We are waiting for the new event schema to be deployed before merging. |
Thank you for the update! Would that be possible to provide an ECD for this feature, when is this going to be available in prod? |
@omerfarukaslan we're looking at around two weeks to get the back end sorted |
Hey @qhanam, thank you for the update. I'd like to share a better function that doesn't run on O(n), as a reference. This function will only check the nearest 5 ancestors, and break when attribute is found. The previously shared function was using JS
|
Hi @qhanam is there any update for this PR? Thank you! |
Hey @omerfarukaslan, we're still sorting out the backend and will most likely deploy the changes to the Web Client by the end of next week. Thanks for checking in and patiently waiting! |
Awesome!! Looking forward to it. Thank you for the update. |
It is safe to merge this now. |
Awesome!! Cannot wait to see it in action. Thank you! |
This PR adds documentation as part of changes introduced in #163.
This commit adds documentation as part of changes introduced in #163.
This commit adds documentation as part of changes introduced in #163.
This commit adds documentation as part of changes introduced in #163.
This commit adds documentation as part of changes introduced in #163.
Issue: As a user, I am unable to get accurate information about some user events when I render my page using React components. When we render certain components, they get rendered with children elements and we don't have access to those child elements to add id or any css locater at all. For example, when I render a it gets rendered like
<button id="myButton"><span>Click Here</span></button>
, and when users click on span element, I see "SPAN" instead of "myButton" on the logs. We can use cssLocators for this but, we would need to add a separate event for each cssLocator which is a lot of manual work and increases code complexity.Solution: I added an optional
data-rum-id
attribute to get logged within eventData as eventData.elementRumId. With this change DomEventPlugin will capture thedata-rum-id
from nearest parent element if the clicked element doesn't have one. And this will only work when a cssLocator is provided.For example:
Interaction event:
{ event: 'click', cssLocator: '[label^="button-"]' }
Code:
<button id="button6" data-rum-id="button-six" label="button-label1"> Button Six </button>
<button id="button7" data-rum-id="button-seven" label="button-label2"> <span>Button Seven</span> </button>
Output
{"details":"{\"version\":\"1.0.0\",\"event\":\"click\",\"elementId\":\"button6\",\"cssLocator\":\"[label^=\\\"button-\\\"]\",\"elementRumId\":\"button-six\"}"}
{"details":"{\"version\":\"1.0.0\",\"event\":\"click\",\"elementId\":\"SPAN\",\"cssLocator\":\"[label^=\\\"button-\\\"]\",\"elementRumId\":\"button-seven\"}"}
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.