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

✨⚗ [RUMF-1355] add selector with stable attributes #1684

Merged
merged 10 commits into from
Aug 11, 2022

Conversation

BenoitZugmeyer
Copy link
Member

@BenoitZugmeyer BenoitZugmeyer commented Aug 9, 2022

Motivation

Use common "stable" attributes to improve CSS selector generation for heatmaps.

Changes

  • refactor how we compute the click action base. This has two purposes:
    • avoid computing selector twice when cloning the Click object
    • helps passing the actionNameAttribute option to the function used to compute selectors
  • tweak types to allow using multiple selectors
  • change selector computation to make it easier to customize
  • add selector with stable attributes

Testing

  • Local
  • Staging
  • Unit
  • End to end

I have gone over the contributing documentation.

@BenoitZugmeyer BenoitZugmeyer force-pushed the benoit/heatmap--stable-attributes-selector branch from 8bb7293 to 2714cd9 Compare August 10, 2022 12:20
@BenoitZugmeyer BenoitZugmeyer force-pushed the benoit/heatmap--stable-attributes-selector branch from 2714cd9 to a47d95b Compare August 10, 2022 12:52
@codecov-commenter
Copy link

codecov-commenter commented Aug 10, 2022

Codecov Report

Merging #1684 (4b24d5a) into main (7009cf2) will increase coverage by 0.01%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##             main    #1684      +/-   ##
==========================================
+ Coverage   90.76%   90.77%   +0.01%     
==========================================
  Files         127      127              
  Lines        4709     4727      +18     
  Branches     1052     1054       +2     
==========================================
+ Hits         4274     4291      +17     
- Misses        435      436       +1     
Impacted Files Coverage Δ
...EventsCollection/action/getSelectorsFromElement.ts 100.00% <100.00%> (ø)
...in/rumEventsCollection/action/trackClickActions.ts 98.05% <100.00%> (+0.01%) ⬆️
packages/core/src/tools/timeUtils.ts 97.29% <0.00%> (-2.71%) ⬇️

📣 Codecov can now indicate which changes are the most critical in Pull Requests. Learn more

@BenoitZugmeyer BenoitZugmeyer marked this pull request as ready for review August 10, 2022 13:21
@BenoitZugmeyer BenoitZugmeyer requested a review from a team as a code owner August 10, 2022 13:21
Copy link
Contributor

@bcaudan bcaudan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

]

export function getSelectorsFromElement(element: Element, actionNameAttribute: string | undefined) {
const attributeSelectors = STABLE_ATTRIBUTES.map((attribute) => getAttributeSelector.bind(null, attribute))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥜 nitpick: ‏we could compute stable attribute selectors once instead of doing it at each call

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ question: why is getAttributeSelector called using bind? also doesn't getAttributeSelector need an arg of type element?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bind does not call the function. bind returns a new function with some arguments defined (cf doc). Basically,

STABLE_ATTRIBUTES.map((attribute) => getAttributeSelector.bind(null, attribute))

is the same as

STABLE_ATTRIBUTES.map((attribute) => (element: Element) => getAttributeSelector(attribute, element))

Would it be clearer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah so we are using it as a form of currying? Thanks for the clarification

I personally wouldn't use bind unless we care about this; but that is more personal preference.

Copy link
Contributor

@liywjl liywjl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work!

]

export function getSelectorsFromElement(element: Element, actionNameAttribute: string | undefined) {
const attributeSelectors = STABLE_ATTRIBUTES.map((attribute) => getAttributeSelector.bind(null, attribute))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ question: why is getAttributeSelector called using bind? also doesn't getAttributeSelector need an arg of type element?

Comment on lines +124 to +136
function isSelectorUniqueGlobally(element: Element, selector: string): boolean {
return element.ownerDocument.body.querySelectorAll(selector).length === 1
}

function isSelectorUniqueAmongChildren(element: Element, selector: string): boolean {
for (let i = 0; i < element.parentElement!.children.length; i++) {
const sibling = element.parentElement!.children[i]
if (sibling !== element && elementMatches(sibling, selector)) {
return false
}
}
return true
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 praise: great naming

Comment on lines +170 to +198
function computeClickActionBase(event: MouseEvent & { target: Element }, actionNameAttribute?: string) {
let target: ClickAction['target']
let position: ClickAction['position']

if (isExperimentalFeatureEnabled('clickmap')) {
const rect = event.target.getBoundingClientRect()
target = assign(
{
width: Math.round(rect.width),
height: Math.round(rect.height),
},
getSelectorsFromElement(event.target, actionNameAttribute)
)
position = {
// Use clientX and Y because for SVG element offsetX and Y are relatives to the <svg> element
x: Math.round(event.clientX - rect.left),
y: Math.round(event.clientY - rect.top),
}
}

return {
type: 'click',
target,
position,
name: getActionNameFromElement(event.target, actionNameAttribute),
event,
startClocks: clocksNow(),
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

@BenoitZugmeyer BenoitZugmeyer merged commit fe9dbb1 into main Aug 11, 2022
@BenoitZugmeyer BenoitZugmeyer deleted the benoit/heatmap--stable-attributes-selector branch August 11, 2022 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants