Skip to content

Commit

Permalink
Merge branch 'main' into redirect-checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
trieloff committed Sep 18, 2024
2 parents a81db0a + fe53369 commit 8318d97
Show file tree
Hide file tree
Showing 14 changed files with 848 additions and 333 deletions.
13 changes: 12 additions & 1 deletion .releaserc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,18 @@ module.exports = {
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
["@semantic-release/github", {}]
["@semantic-release/github", {
"assets": [
{
"path": "src/index.js",
"label": "RUM Enhancer JS"
},
{
"path": "src/index.md5",
"label": "RUM Enhancer Hash"
}
]
}]
],
branches: ['main'],
};
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
## [2.21.1](https://github.com/adobe/helix-rum-enhancer/compare/v2.21.0...v2.21.1) (2024-09-18)


### Bug Fixes

* **release:** do not attempt to create hash from source map ([0ba7631](https://github.com/adobe/helix-rum-enhancer/commit/0ba763158ee7760ec405a0a0e2d6c32e8911d6c2))

# [2.21.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.20.0...v2.21.0) (2024-09-18)


### Bug Fixes

* **dom:** apply change ([67803e6](https://github.com/adobe/helix-rum-enhancer/commit/67803e60f57ef29b9ea1c51eea26d99c778b5f97))
* **dom:** better handling of empty type form inputs ([d53deb5](https://github.com/adobe/helix-rum-enhancer/commit/d53deb5c6e94ff18df2a01996c39b5cff7636fe4))
* **dom:** better handling of fake blocks ([66a9049](https://github.com/adobe/helix-rum-enhancer/commit/66a90497a45d754ed0fa4e8f8bc0917be64b3245))


### Features

* **checkpoints:** add support for the `language` checkpoint ([f99755a](https://github.com/adobe/helix-rum-enhancer/commit/f99755a5cc8eef83b655a6beff5e7e371196c90d))
* **dom:** more fine-grained sourceselector implementation ([ada279d](https://github.com/adobe/helix-rum-enhancer/commit/ada279d423590fb3feeb304b3a57d6da4ee3c9bf))

# [2.20.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.19.2...v2.20.0) (2024-08-19)


Expand Down
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,49 @@ It will add following new checkpoints:
- `reload`, `navigate`, `enter`: depending on how the current page was accessed
- `formsubmit`: when a form is submitted

### The `source` parameter and the `sourceSelector`

The `source` parameter is a string that can be used to identify the source of the event. It can be used to identify the source of the event, e.g. a button, a link, a form, etc.
It represents an idealized CSS selector that is both human-readable and specific enough to identify the source of the event in the document, even when not having access to the
orginal document. It is idealized because it pretends the DOM would use modern HTML with concise semantics, even if the actual document uses `class` values for things that would
be better represented by semantic HTML elements.

The `sourceSelector` function is a function that takes a DOM element and returns a descriptive `source` parameter. If the element has a `data-rum-source` attribute, its value is used as the `source` parameter. Otherwise, the function tries to generate a `source` parameter based on the element's tag name, class names, and text content.

The structure of the `source` parameter is as follows:

```
<context> <element>#<identifier>
```
All three parts are optional

`context` is
- `form` for form elements
- `dialog` for dialog elements, or parent containers that are fixed positioned and have a positive high z-index
- `.block-name` for Helix blocks
- `header`, `footer`, `nav`, `aside` for main site structure
- `#id` as a fallback, if a container ID is available

`element` is
- `button` for buttons, or links that look like buttons (e.g. with a class `button` or `btn` or `cta`)
- `img` for images
- `video` for videos
- `a` for links that are not buttons
- `input[type="text"]` for input elements (all types are supported)
- `select`, `textarea`, etc. for other form elements

`identifier` is
- the `id` attribute of the element, if provided
- the first `.class` if there are any
- else omitted

Even if an `identifier` is provided, having a `context` and `element` is recommended, as it makes the `source` parameter more readable and easier to understand.


#### Examples

- ``

## Development

### Build
Expand Down
92 changes: 57 additions & 35 deletions modules/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,59 @@ export const targetSelector = (element) => {
}
};

function walk(element, checkFn) {
if (!element || element === document.body || element === document.documentElement) {
return undefined;
}
const checkValue = checkFn(element);
return checkValue || walk(element.parentElement, checkFn);
}

function isDialog(element) {
// doing it well
if (element.tagName === 'DIALOG') return true;
// making the best of it
if (element.getAttribute('role') === 'dialog') return true;
if (element.getAttribute('role') === 'alertdialog') return true;
if (element.getAttribute('aria-modal') === 'true') return true;
// doing it wrong
const computedStyle = window.getComputedStyle(element);
return (computedStyle && computedStyle.position === 'fixed' && computedStyle.zIndex > 100);
}

function isButton(element) {
if (element.tagName === 'BUTTON') return true;
if (element.tagName === 'INPUT' && element.getAttribute('type') === 'button') return true;
if (element.tagName === 'A') {
const classes = Array.from(element.classList);
return classes.some((className) => className.match(/button|cta/));
}
return element.getAttribute('role') === 'button';
}

function getSourceContext(element) {
if (element.closest('form')) return 'form';
const block = element.closest('.block[data-block-name]');
if (block) return `.${block.getAttribute('data-block-name')}`;
if (walk(element, isDialog)) return 'dialog';
if (element.closest('nav')) return 'nav';
if (element.closest('header')) return 'header';
if (element.closest('footer')) return 'footer';
if (element.closest('aside')) return 'aside';
return (walk(element, (e) => e.id && `#${e.id}`));
}

function getSourceElement(element) {
if (element.closest('form') && Array.from(element.closest('form').elements).includes(element)) return element.tagName.toLowerCase() + (element.tagName === 'INPUT' ? `[type='${element.getAttribute('type') || ''}']` : '');
if (walk(element, isButton)) return 'button';
return element.tagName.toLowerCase().match(/^(a|img|video)$/) && element.tagName.toLowerCase();
}

function getSourceIdentifier(element) {
if (element.id) return `#${element.id}`;
if (element.getAttribute('data-block-name')) return `.${element.getAttribute('data-block-name')}`;
return (element.classList.length > 0 && `.${element.classList[0]}`);
}
export const sourceSelector = (element) => {
try {
if (!element || element === document.body || element === document.documentElement) {
Expand All @@ -38,41 +91,10 @@ export const sourceSelector = (element) => {
if (element.getAttribute('data-rum-source')) {
return element.getAttribute('data-rum-source');
}
const form = element.closest('form');
let formElementSelector = '';
if (form && Array.from(form.elements).includes(element)) {
formElementSelector = element.tagName === 'INPUT' ? `form input[type='${element.getAttribute('type')}']` : `form ${element.tagName.toLowerCase()}`;
}

const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
if (element.id || formElementSelector) {
const id = element.id ? `#${element.id}` : '';
return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
}

if (element.getAttribute('data-block-name')) {
return `.${element.getAttribute('data-block-name')}`;
}

const classes = Array.from(element.classList);
const label = element.tagName.toLowerCase();
const firstClass = classes.length > 0 ? `.${classes[0]}` : '';
const labelWithClass = `${element.tagName.toLowerCase()}${firstClass}`;
if (element.tagName.toLowerCase() === 'button'
|| element.type === 'button'
|| classes.some((className) => className.match(/button|cta/))) {
let parent = element.parentElement;
if (!parent) return labelWithClass;
if (parent.id) return `#${parent.id} ${label}`;
while (parent.tagName !== 'BODY' && !parent.id) parent = parent.parentElement;
if (parent.id) return `#${parent.id} ${labelWithClass}`;
return blockName ? `.${blockName} ${labelWithClass}` : labelWithClass;
}

const parent = sourceSelector(element.parentElement);
if (parent) return parent;

return labelWithClass;
const context = getSourceContext(element.parentElement) || '';
const elementName = getSourceElement(element) || '';
const identifier = getSourceIdentifier(element) || '';
return `${context} ${elementName}${identifier}`.trim() || `"${element.textContent.substring(0, 10)}"`;
/* c8 ignore next 3 */
} catch (error) {
return null;
Expand Down
9 changes: 7 additions & 2 deletions modules/martech.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@ export function addCookieConsentTracking(sampleRUM) {

let consentMutationObserver;
const trackShowConsent = () => {
if (document.querySelector('body > div#onetrust-consent-sdk')) {
sampleRUM('consent', { source: 'onetrust', target: 'show' });
const otsdk = document.querySelector('body > div#onetrust-consent-sdk');
if (otsdk) {
if (otsdk.checkVisibility && !otsdk.checkVisibility()) {
sampleRUM('consent', { source: 'onetrust', target: 'suppressed' });
} else {
sampleRUM('consent', { source: 'onetrust', target: 'show' });
}
if (consentMutationObserver) {
consentMutationObserver.disconnect();
}
Expand Down
Loading

0 comments on commit 8318d97

Please sign in to comment.