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

Fix support for VWO #88

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
193 changes: 123 additions & 70 deletions chrome-extension/src/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// More information at https://lukasvermeer.nl/srm/

// Generic (non-platform specific) functions
const platform = window.location.host;
const platform = window.location.host.replace(/www./, "");

// Define object to contain platform specific methods.
const platforms = {
Expand Down Expand Up @@ -61,7 +61,7 @@ const platforms = {
}
newIframe();
srmChecked = false;
chrome.runtime.sendMessage({srmStatus: 'ON'});
chrome.runtime.sendMessage({ srmStatus: 'ON' });
}
},
);
Expand Down Expand Up @@ -137,7 +137,7 @@ const platforms = {
}
newIframe();
srmChecked = false;
chrome.runtime.sendMessage({srmStatus: 'ON'});
chrome.runtime.sendMessage({ srmStatus: 'ON' });
}
},
);
Expand Down Expand Up @@ -188,86 +188,139 @@ const platforms = {
// VWO
'app.vwo.com': {
init() {
// Load the Settings > Others view in the background to get expected proportion of variants.
function newIframe() {
if (location.href.slice(-6) === 'report') {
const oldIframe = document.getElementById('iframeforweight');
if (oldIframe != null) {
oldIframe.parentNode.removeChild(oldIframe);
}
const ifrm = document.createElement('iframe');
ifrm.id = 'iframeforweight';
ifrm.src = `${location.href.slice(0, -7)}/edit/others`;
ifrm.style.display = 'none';
document.body.appendChild(ifrm);
}
}
newIframe();
// Listen for changing URL to reload iframe for proportions
chrome.runtime.onMessage.addListener(
(request, sender, sendResponse) => {
if (request.message === 'URL has changed') {
const srmstyling = document.getElementById('srmcss');
if (srmstyling != null) {
srmstyling.parentNode.removeChild(srmstyling);
}
newIframe();
loadSRM(true);
srmChecked = false;
chrome.runtime.sendMessage({srmStatus: 'ON'});
chrome.runtime.sendMessage({ srmStatus: 'ON' });
}
},
);
let srmChecked = false; // TODO: Listen for changes to do check when content loads.
setInterval(() => {
if (!srmChecked) {
// Get sample counts
const d = document.querySelector('table.table--data tbody.ng-scope').querySelectorAll('tr.ng-scope strong.ng-binding');
const iframeforweight = document.getElementById('iframeforweight');
if (iframeforweight === null) return;
const weightnodes = iframeforweight.contentWindow.document.querySelector('div[label="Traffic Split"]');
if (d && weightnodes) {
const sessioncounts = [];
const weights = [];
function loadSRM(urlChanged) {
const cssid = 'srmcss';
const styleTag = document.getElementById(cssid);
if (styleTag) {
document.body.removeChild(styleTag);
}
const notAllowed = ['target', 'deploy']
if (location.href.slice(-6) !== 'report' || notAllowed.some(item => location.href.includes(item))) return;
let srmScript = document.getElementById('srm_script');
if (srmScript && !urlChanged) {
return;
}

// SESSIONS: Fill array
for (let i = 0; i < d.length; i += 1) {
const sessions = parseInt(d[i].innerText.substring(d[i].innerText.indexOf('/') + 2), 10);
sessioncounts.push(sessions);
}
window.addEventListener('srm_loaded', (e) => {
const { variations, goals, selectedGoal } = JSON.parse(e.detail);
let enabledVariations = variations.filter((item) => item.isDisabled == false).map(item => item.id);

// WEIGHTS: Check if equal or custom weight splits are used and fill array
const weightEqual = iframeforweight.contentWindow.document.querySelectorAll('div[label="Traffic Split"] ul input')[0].checked;
if (weightEqual) {
for (let i = 0; i < d.length; i += 1) {
const weightnode = parseFloat(100 / d.length, 10);
weights.push(weightnode);
}
} else {
for (let i = 0; i < d.length; i += 1) {
const weightnode = parseFloat(weightnodes.querySelectorAll('div > ul > li > ul > li > button > span.ng-binding')[i].innerText, 10);
weights.push(weightnode);
}
}
const weights = [];

enabledVariations.forEach((variation) => {
weights.push(variations.filter((item) => item.id == variation)[0].percentSplit);
})


const sessionCount = [];

enabledVariations.forEach((variation) => {
sessionCount.push(goals.filter((item) => item.variation == variation && item.goal == selectedGoal)[0]?.aggregated?.visitorCount || 0);
});

if (sessionCount.length > 0 || weights.length > 0) {
// Do SRM Check
checkSRM(sessioncounts, weights);
srmChecked = true;
checkSRM(sessionCount, weights);
}
srmChecked = true;
})

// create a script and inject to app and emit events to get the campaign data
const script = document.createElement('script');
if (urlChanged && srmScript) {
script.innerHTML = `
setTimeout(sendCompaign,1000);
`
}
}, 1000);
else {
script.innerHTML = `
function sendCompaign(){
const campaign = window.angular?.element(document.querySelector('div[ng-controller="CampaignReportNewController"]')).scope()?.campaign;
if(campaign){
var event = new CustomEvent('srm_loaded', {
detail: JSON.stringify({goals:campaign.variationGoalData,variations:campaign.variations, selectedGoal: window.angular.element(document.querySelector('div[ng-controller="CampaignReportNewController"]')).scope().selectedGoal }),
bubbles: true, // Allow the event to bubble up through the DOM
cancelable: true // Allow the event to be canceled
});
window.dispatchEvent(event);
}
else{
setTimeout(sendCompaign,1000);
}
}
sendCompaign();
`;
script.id = 'srm_script';
}
document.body.appendChild(script);
}
// allowing page to load
setTimeout(loadSRM, 2000);
},
flagSRM(pval) {
const temp_styles = 'table.table--data tbody.ng-scope tr.ng-scope strong.ng-binding {background-color: red; color: white; padding: 1px 3px; border-radius: 3px;} td[child-order-id="conversionsVisitors"] div:nth-of-type(2) span {background-color: red; color: white; padding: 1px 3px; border-radius: 3px;}';
var srm_css = document.createElement('style');
srm_css.type = 'text/css';
srm_css.id = 'srmcss';
srm_css.appendChild(document.createTextNode(temp_styles));
document.getElementsByTagName('body')[0].appendChild(srm_css);
document.querySelector('table.table--data tbody.ng-scope').querySelectorAll('tr.ng-scope strong.ng-binding').forEach(i => i.title = `SRM detected! p-value = ${pval}`);
document.querySelector('table.table--data tbody.ng-scope').querySelectorAll('td[child-order-id="conversionsVisitors"] div:nth-of-type(2) span').forEach(i => i.title = `SRM detected! p-value = ${pval}`);
const id = 'srmcss';
if (!document.getElementById(id)) {
const temp_styles = 'table.table--data tbody.ng-scope tr.ng-scope strong.ng-binding {background-color: red; color: white; padding: 1px 3px; border-radius: 3px;} td[child-order-id="conversionsVisitors"] div:nth-of-type(2) span {background-color: red; color: white; padding: 1px 3px; border-radius: 3px;}';
var srm_css = document.createElement('style');
srm_css.type = 'text/css';
srm_css.id = id;
srm_css.appendChild(document.createTextNode(temp_styles));
document.getElementsByTagName('body')[0].appendChild(srm_css);
}

function addTitle() {
let shouldResetTitle = false;
document.querySelectorAll('td[child-order-id="conversionsVisitors"]').forEach(i => {
if (!i.getAttribute('title')) {
if (!shouldResetTitle) {
shouldResetTitle = true;
}
i.setAttribute('title', `SRM detected! p-value = ${pval}`);
}
});
if (shouldResetTitle) {
setTimeout(addTitle, 2000);
}
}
addTitle();
},
unflagSRM() {
unflagSRM(pval) {
// TODO remove SRM warning if needed.
const id = 'srmcss';
if (!document.getElementById(id)) {
const temp_styles = 'table.table--data tbody.ng-scope tr.ng-scope strong.ng-binding {background-color: green; color: white; padding: 1px 3px; border-radius: 3px;} td[child-order-id="conversionsVisitors"] div:nth-of-type(2) span {background-color: green; color: white; padding: 1px 3px; border-radius: 3px;}';
var srm_css = document.createElement('style');
srm_css.type = 'text/css';
srm_css.id = id;
srm_css.appendChild(document.createTextNode(temp_styles));
document.getElementsByTagName('body')[0].appendChild(srm_css);
}
function addTitle() {
let shouldResetTitle = false;
document.querySelectorAll('td[child-order-id="conversionsVisitors"]').forEach(i => {
if (!i.getAttribute('title')) {
if (!shouldResetTitle) {
shouldResetTitle = true;
}
i.setAttribute('title', `p-value = ${pval}`);
}
});
if (shouldResetTitle) {
setTimeout(addTitle, 2000);
}
}
addTitle();
},
},

Expand All @@ -280,7 +333,7 @@ const platforms = {
if (request.message === 'URL has changed') {
newIframe();
srmChecked = false;
chrome.runtime.sendMessage({srmStatus: 'ON'});
chrome.runtime.sendMessage({ srmStatus: 'ON' });
}
},
);
Expand Down Expand Up @@ -354,7 +407,7 @@ const platforms = {
if (request.message === 'URL has changed') {
newIframe();
srmChecked = false;
chrome.runtime.sendMessage({srmStatus: 'ON'});
chrome.runtime.sendMessage({ srmStatus: 'ON' });
}
},
);
Expand Down Expand Up @@ -407,7 +460,7 @@ const platforms = {
if (request.message === 'URL has changed') {
newIframe();
srmChecked = false;
chrome.runtime.sendMessage({srmStatus: 'ON'});
chrome.runtime.sendMessage({ srmStatus: 'ON' });
}
},
);
Expand Down Expand Up @@ -485,7 +538,7 @@ const platforms = {
if (request.message === 'URL has changed') {
newIframe();
srmChecked = false;
chrome.runtime.sendMessage({srmStatus: 'ON'});
chrome.runtime.sendMessage({ srmStatus: 'ON' });
}
},
);
Expand Down Expand Up @@ -572,7 +625,7 @@ const platforms = {
if (request.message === 'URL has changed') {
newIframe();
srmChecked = false;
chrome.runtime.sendMessage({srmStatus: 'ON'});
chrome.runtime.sendMessage({ srmStatus: 'ON' });
}
},
);
Expand Down Expand Up @@ -634,4 +687,4 @@ const platforms = {
},
};

platforms[platform].init();
platforms[platform].init();
4 changes: 2 additions & 2 deletions chrome-extension/src/srm.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function computeSRM(observed, expected) {
// Degrees of freedom is variations - 1
const df = observed.length - 1;
// Total sample size is sum of all variations
const sampleSize = observed.reduce((a, v) => a + v);
const sampleSize = observed.length>0 ? observed.reduce((a, v) => a + v) : 0;
// Scale expected count per variation to match observed sample
const expectedScaled = expected.map((e) => Math.round((sampleSize * e) / 100));
// Chi-square is sum of squares of observed - expected over expected for each variation
Expand All @@ -37,7 +37,7 @@ function checkSRM(observed, expected) {
chrome.runtime.sendMessage({srmStatus: 'SRM'});
}
} else {
platforms[platform].unflagSRM();
platforms[platform].unflagSRM(pval);
if (chrome.runtime) {
chrome.runtime.sendMessage({srmStatus: 'OK'});
}
Expand Down