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

Add LRC functionality #20

Merged
merged 32 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
60279b5
start adding the beaconlrc class
wordpressfan Aug 16, 2024
fd08e63
Merge branch 'develop' into feature/lrc-script
wordpressfan Aug 19, 2024
1e7fbec
finalize the beacon manager class
wordpressfan Aug 19, 2024
4409307
Merge branch 'develop' into feature/lrc-script
wordpressfan Aug 20, 2024
faab7a1
temporarily fix tests
wordpressfan Aug 20, 2024
3e36876
Return only hash as payload
jeawhanlee Aug 21, 2024
502aaac
Remove duplicate payload
jeawhanlee Aug 21, 2024
7dff5df
Only add child element below the fold threshold and parent elements o…
jeawhanlee Aug 23, 2024
b4f633b
use config threshold instead of static value
wordpressfan Aug 23, 2024
ba661b6
Revert "use config threshold instead of static value"
wordpressfan Aug 23, 2024
56d2d91
use logger class and comment the xpath for now
wordpressfan Aug 23, 2024
81f0222
Replace static values with config
jeawhanlee Aug 23, 2024
5609874
Merge commit 'ba590ed75fe1645152c0a42894206f33b31d22c4' into feature/…
MathieuLamiot Aug 26, 2024
f8f26e0
start doing as grooming
wordpressfan Aug 28, 2024
fc76e98
get only elements that have hash
wordpressfan Aug 28, 2024
96cb6c5
Merge pull request #26 from wp-media/enhancement/depend-on-php
MathieuLamiot Aug 30, 2024
bcc63cc
Added test for lrc
jeawhanlee Sep 2, 2024
e6c6ecd
Best practices
jeawhanlee Sep 2, 2024
a111d6b
Additional best practices
jeawhanlee Sep 2, 2024
9635089
start considering viewport on element distance calculation
wordpressfan Sep 3, 2024
ecdac7a
adjust imports and the log message
wordpressfan Sep 4, 2024
6463cee
Merge pull request #28 from wp-media/fix/consider-viewport-on-calcula…
MathieuLamiot Sep 5, 2024
5455ec2
change the structure of test to handle more cases and use sinon for m…
wordpressfan Sep 9, 2024
83ff742
test
wordpressfan Sep 9, 2024
82e19b5
test 2
wordpressfan Sep 9, 2024
9004d72
Merge branch 'feature/lrc-script' into enhancement/23-add-tests-to-in…
wordpressfan Sep 9, 2024
8bfde19
fix tests attempt 1
wordpressfan Sep 9, 2024
3e7be55
add more tests
wordpressfan Sep 9, 2024
2bc5ca4
add tests for run method
wordpressfan Sep 9, 2024
e050e9e
add more tests
wordpressfan Sep 9, 2024
8f50b8f
remove not used argument
wordpressfan Sep 9, 2024
13a78bf
Merge pull request #27 from wp-media/enhancement/23-add-tests-to-incr…
MathieuLamiot Sep 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions src/BeaconLrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
'use strict';

import BeaconUtils from "./Utils.js";
import BeaconManager from "./BeaconManager.js";

class BeaconLrc {
constructor(config, logger) {
this.config = config;
this.logger = logger;
this.lazyRenderElements = [];
}

async run() {
try {
const elementsInView = this._getLazyRenderElements();
if (elementsInView) {
this._processElements(elementsInView);
}
} catch (err) {
this.errorCode = 'script_error';
this.logger.logMessage('Script Error: ' + err);
}
}

_getLazyRenderElements() {
const elements = document.querySelectorAll(this.config.elements);

if (elements.length <= 0) {
return [];
}

const validElements = Array.from(elements).filter(element => !this._skipElement(element));

return validElements.map(element => ({
element: element,
depth: this._getElementDepth(element),
distance: this._getElementDistance(element),
hash: this._getLocationHash(element)
}));
}

_getElementDepth(element) {
let depth = 0;
let parent = element.parentElement;
while (parent) {
depth++;
parent = parent.parentElement;
}
return depth;
}

_getElementDistance(element) {
const rect = element.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return rect.top + scrollTop - (window.innerHeight || document.documentElement.clientHeight);
}

_skipElement(element) {
const skipStrings = this.config.skipStrings || ['memex'];
if (!element || !element.id) return false;
return skipStrings.some(str => element.id.toLowerCase().includes(str));
}

_shouldSkipElement(element, exclusions) {
if (!element) return false;
for (let i = 0; i < exclusions.length; i++) {
const [attribute, pattern] = exclusions[i];
const attributeValue = element.getAttribute(attribute);
if (attributeValue && new RegExp(pattern, 'i').test(attributeValue)) {
return true;
}
}
return false;
}

_processElements(elements) {
elements.forEach(({ element, depth, distance, hash }) => {
if (this._shouldSkipElement(element, this.config.exclusions || [])) {
return;
}
if ( 'No hash detected' !== hash ) {
this.lazyRenderElements.push( hash );
}

const style = distance > 1800 ? 'color: green;' : distance === 0 ? 'color: red;' : '';
console.log(`%c${'\t'.repeat(depth)}${element.tagName} (Depth: ${depth}, Distance from viewport top: ${distance}px)`, style);

const xpath = this._getXPath(element);
console.log(`%c${'\t'.repeat(depth)}Xpath: ${xpath}`, style);

console.log(`%c${'\t'.repeat(depth)}Location hash: ${hash}`, style);

console.log(`%c${'\t'.repeat(depth)}Dimensions Client Height: ${element.clientHeight}`, style);
});
}

_getXPath(element) {
if (element.id !== "") {
return `//*[@id="${element.id}"]`;
}

return this._getElementXPath(element);
}

_getElementXPath(element) {
if (element === document.body) {
return '/html/body';
}
const position = this._getElementPosition(element);
return `${this._getElementXPath(element.parentNode)}/${element.nodeName.toLowerCase()}[${position}]`;
}

_getElementPosition(element) {
let pos = 1;
let sibling = element.previousElementSibling;
while (sibling) {
if (sibling.nodeName === element.nodeName) {
pos++;
}
sibling = sibling.previousElementSibling;
}
return pos;
}

_getLocationHash(element) {
return element.hasAttribute('data-rocket-location-hash')
? element.getAttribute('data-rocket-location-hash')
: 'No hash detected';
}

getResults() {
return this.lazyRenderElements;
}
}

export default BeaconLrc;
21 changes: 16 additions & 5 deletions src/BeaconManager.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
'use strict';

import BeaconLcp from "./BeaconLcp.js";
import BeaconLrc from "./BeaconLrc.js";
import BeaconUtils from "./Utils.js";
import Logger from "./Logger.js";

class BeaconManager {
constructor(config) {
this.config = config;
this.lcpBeacon = null;
this.lrcBeacon = null;
this.infiniteLoopId = null;
this.errorCode = '';
this.logger = new Logger(this.config.debug);
Expand All @@ -25,21 +27,29 @@ class BeaconManager {
}, 10000);

const isGeneratedBefore = await this._getGeneratedBefore();
let shouldSaveResultsIntoDB = false;

// OCI / LCP / ATF
const shouldGenerateLcp = (
this.config.status.atf && (isGeneratedBefore === false || isGeneratedBefore.lcp === false)
);
const shouldGeneratelrc = (
this.config.status.lrc && (isGeneratedBefore === false || isGeneratedBefore.lrc === false)
);
if (shouldGenerateLcp) {
this.lcpBeacon = new BeaconLcp(this.config, this.logger);
await this.lcpBeacon.run();
shouldSaveResultsIntoDB = true;
} else {
this.logger.logMessage('Not running BeaconLcp because data is already available');
this.logger.logMessage('Not running BeaconLcp because data is already available or feature is disabled');
}

if (shouldGeneratelrc) {
this.lrcBeacon = new BeaconLrc(this.config, this.logger);
await this.lrcBeacon.run();
} else {
this.logger.logMessage('Not running BeaconLrc because data is already available or feature is disabled');
}

if (shouldSaveResultsIntoDB) {
if (shouldGenerateLcp || shouldGeneratelrc) {
this._saveFinalResultIntoDB();
} else {
this.logger.logMessage("Not saving results into DB as no beacon features ran.");
Expand Down Expand Up @@ -84,7 +94,8 @@ class BeaconManager {

_saveFinalResultIntoDB() {
const results = {
lcp: this.lcpBeacon ? this.lcpBeacon.getResults() : null
lcp: this.lcpBeacon ? this.lcpBeacon.getResults() : null,
lrc: this.lrcBeacon ? this.lrcBeacon.getResults() : null
};

const data = new FormData();
Expand Down
2 changes: 1 addition & 1 deletion test/BeaconManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ describe('BeaconManager', function() {
assert.strictEqual(sentDataObject['url'], config.url);
assert.strictEqual(sentDataObject['is_mobile'], config.is_mobile.toString());
// For complex types like arrays or objects, you might need to parse them before assertion
const expectedResults = JSON.parse(JSON.stringify({lcp : beacon.lcpBeacon.performanceImages}));
const expectedResults = JSON.parse(JSON.stringify({lcp : beacon.lcpBeacon.performanceImages, lrc: null}));
assert.deepStrictEqual(JSON.parse(sentDataObject['results']), expectedResults);
assert.strictEqual(sentDataObject['status'], beacon._getFinalStatus());
sinon.assert.calledOnce(finalizeSpy);
Expand Down
Loading