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

feat(rum-explorer): display conversion percentage per facet entry #574

Merged
merged 6 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
2 changes: 1 addition & 1 deletion tools/rum/charts/sankey.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ const stages = [
detect: (bundle, dataChunks) => {
const conversionSpec = parseConversionSpec();
if (Object.keys(conversionSpec).length === 0) return false;
if (Object.keys(conversionSpec).length === 1 && conversionSpec.checkpoint[0] === 'click') return false;
if (Object.keys(conversionSpec).length === 1 && conversionSpec.checkpoint && conversionSpec.checkpoint[0] === 'click') return false;
return dataChunks.hasConversion(bundle, conversionSpec, 'every');
},
next: [],
Expand Down
16 changes: 14 additions & 2 deletions tools/rum/elements/list-facet.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { scoreCWV, toHumanReadable, escapeHTML } from '../utils.js';
import {
computeConversionRate, escapeHTML, scoreCWV, toHumanReadable,
} from '../utils.js';
import { pValue } from '../cruncher.js';

async function addSignificanceFlag(element, metric, baseline) {
Expand Down Expand Up @@ -210,7 +212,17 @@ export default class ListFacet extends HTMLElement {
countspan.textContent = toHumanReadable(entry.metrics.pageViews.sum);
countspan.title = entry.metrics.pageViews.sum;
const valuespan = this.createValueSpan(entry);
label.append(valuespan, countspan);

const conversionspan = document.createElement('span');
conversionspan.className = 'extra';

const conversions = entry.metrics.conversions.sum;
const visits = entry.metrics.visits.sum;
const conversionRate = computeConversionRate(conversions, visits);
conversionspan.textContent = toHumanReadable(conversionRate);
conversionspan.title = conversionRate;

akalfas marked this conversation as resolved.
Show resolved Hide resolved
label.append(valuespan, countspan, conversionspan);

const ul = document.createElement('ul');
ul.classList.add('cwv');
Expand Down
47 changes: 31 additions & 16 deletions tools/rum/rum-slicer.css
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ main .key-metrics ul > #visits p span.extra::after {

conversion-tracker {
display: grid;
grid-template-areas:
'heading heading button'
grid-template-areas:
'heading heading button'
'number empty button'
'number definition definition'
;
Expand Down Expand Up @@ -383,8 +383,8 @@ figcaption > span {
background-color: var(--light-purple);
}

.filter-tags > span.filter-tag-lcp,
.filter-tags > span.filter-tag-cls,
.filter-tags > span.filter-tag-lcp,
.filter-tags > span.filter-tag-cls,
.filter-tags > span.filter-tag-inp,
.filter-tags > span.filter-tag-conversions,
.filter-tags > span.filter-tag-visits {
Expand Down Expand Up @@ -568,15 +568,17 @@ vitals-facet label::before {
word-break: break-all;
}

#facets fieldset label span.count {
#facets fieldset label span.count, #facets fieldset label span.extra {
opacity: 0.6;
}

#facets fieldset div:hover span.count {
#facets fieldset div:hover span.count, #facets fieldset div:hover span.extra {
opacity: 1;
}

#facets fieldset label span.count::before, #facets fieldset label span.value::before {
#facets fieldset label span.count::before,
#facets fieldset label span.value::before,
#facets fieldset label span.extra::before {
content: ' (';
}

Expand All @@ -588,6 +590,19 @@ vitals-facet label::before {
content: ')';
}

#facets fieldset label span.extra::after {
content: '%)';
}

#facets fieldset span.interesting {
background: orange;
}

#facets fieldset span.significant {
background: red;
}

akalfas marked this conversation as resolved.
Show resolved Hide resolved

#facets literal-facet label span.value::after {
content: '';
}
Expand Down Expand Up @@ -615,7 +630,7 @@ thumbnail-facet fieldset div:not(.more-container) {

vitals-facet fieldset {
display: grid;
grid-template-areas:
grid-template-areas:
'. lcp-good cls-good inp-good'
'. lcp-ni cls-ni inp-ni'
'. lcp-poor cls-poor inp-poor';
Expand Down Expand Up @@ -759,7 +774,7 @@ vitals-facet fieldset label {
}

#facets fieldset div.load-more {
grid-column: 2 / span 2;
grid-column: 2 / span 2;
display: block;
padding: 10px 0;
}
Expand Down Expand Up @@ -994,18 +1009,18 @@ facet-sidebar[aria-disabled="true"] div.quick-filter {
main .key-metrics ul > * h2, main .key-metrics ul > * button {
font-size: 14px;
}

main .key-metrics ul > * h2::before {
width: 14px;
height: 14px;
border-radius: 14px;
}

main .key-metrics ul > * p {
font-size: 30px;
margin: 16px 0;
}

main .key-metrics ul {
grid-gap: 16px;
}
Expand All @@ -1031,7 +1046,7 @@ facet-sidebar[aria-disabled="true"] div.quick-filter {
border-radius: 16px;
padding: 4px 16px;
font-size: 18px;
}
}

#facets link-facet a .protocol {
display: inline;
Expand All @@ -1054,7 +1069,7 @@ facet-sidebar[aria-disabled="true"] div.quick-filter {

main .key-metrics ul {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}

#deepmain > div, #deepmain > facet-sidebar {
width: 50%;
Expand Down Expand Up @@ -1095,7 +1110,7 @@ facet-sidebar[aria-disabled="true"] div.quick-filter {
@media (min-width: 2000px) {
main .key-metrics ul {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
}

#facets link-facet a .protocol {
display: inline;
Expand All @@ -1108,4 +1123,4 @@ facet-sidebar[aria-disabled="true"] div.quick-filter {

footer.footer-wrapper.appear {
display: none;
}
}
26 changes: 16 additions & 10 deletions tools/rum/slicer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import { DataChunks } from './cruncher.js';
import DataLoader from './loader.js';
import {
parseConversionSpec, parseSearchParams, isKnownFacet, scoreCWV, toHumanReadable,
parseConversionSpec,
parseSearchParams,
isKnownFacet,
scoreCWV,
toHumanReadable,
computeConversionRate,
} from './utils.js';

/* globals */
Expand Down Expand Up @@ -36,7 +41,9 @@ dataChunks.addSeries('lcp', (bundle) => bundle.cwvLCP);
dataChunks.addSeries('cls', (bundle) => bundle.cwvCLS);
dataChunks.addSeries('inp', (bundle) => bundle.cwvINP);
dataChunks.addSeries('ttfb', (bundle) => bundle.cwvTTFB);

dataChunks.addSeries('conversions', (bundle) => (dataChunks.hasConversion(bundle, parseConversionSpec())
? bundle.weight
: 0));
function setDomain(domain, key) {
DOMAIN = domain;
loader.domain = domain;
Expand All @@ -59,8 +66,8 @@ export function updateKeyMetrics(keyMetrics) {

document.querySelector('#conversions p').textContent = toHumanReadable(keyMetrics.conversions);
const conversionsExtra = document.createElement('span');
conversionsExtra.textContent = toHumanReadable((
100 * keyMetrics.conversions) / keyMetrics.pageViews);
const conversionRate = computeConversionRate(keyMetrics.conversions, keyMetrics.visits);
conversionsExtra.textContent = toHumanReadable(conversionRate);
conversionsExtra.className = 'extra';
document.querySelector('#conversions p').appendChild(conversionsExtra);

Expand All @@ -81,7 +88,9 @@ const conversionSpec = Object.keys(parseConversionSpec()).length
? parseConversionSpec()
: { checkpoint: ['click'] };

const isDefaultConversion = Object.keys(conversionSpec).length === 1 && conversionSpec.checkpoint[0] === 'click';
const isDefaultConversion = Object.keys(conversionSpec).length === 1
&& conversionSpec.checkpoint
&& conversionSpec.checkpoint[0] === 'click';

function updateDataFacets(filterText, params, checkpoint) {
dataChunks.resetFacets();
Expand Down Expand Up @@ -148,7 +157,7 @@ function updateDataFacets(filterText, params, checkpoint) {
// source and target, the same applies to defined conversion checkpoints
// we need facets for source and target, too
Array.from(new Set([...checkpoint, ...(
isDefaultConversion ? [] : conversionSpec.checkpoint)]))
isDefaultConversion ? [] : conversionSpec.checkpoint || [])]))
.forEach((cp) => {
dataChunks.addFacet(`${cp}.source`, (bundle) => Array.from(
bundle.events
Expand Down Expand Up @@ -222,16 +231,13 @@ export async function draw() {

await herochart.draw();

const facets = dataChunks.facets.conversions;
const converted = facets?.find((f) => f.value === 'converted');

updateKeyMetrics({
pageViews: dataChunks.totals.pageViews.sum,
lcp: dataChunks.totals.lcp.percentile(75),
cls: dataChunks.totals.cls.percentile(75),
inp: dataChunks.totals.inp.percentile(75),
ttfb: dataChunks.totals.ttfb.percentile(75),
conversions: converted ? converted.weight : 0,
conversions: dataChunks.totals.conversions.sum,
visits: dataChunks.totals.visits.sum,
bounces: dataChunks.totals.bounces.sum,
});
Expand Down
29 changes: 28 additions & 1 deletion tools/rum/test/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { truncate, escapeHTML } from '../utils.js';
import { truncate, escapeHTML, computeConversionRate } from '../utils.js';

describe('truncate', () => {
it('truncates to the beginning of the hour', () => {
Expand Down Expand Up @@ -46,3 +46,30 @@ describe('escapeHTML', () => {
assert.strictEqual(escapeHTML('<div>hello</div>'), '&#60;div&#62;hello&#60;/div&#62;');
});
});

describe('computeConversionRate', () => {
it('its 10% for 1 conversion and 10 visits', () => {
const result = computeConversionRate(1, 10);
assert.strictEqual(result, 10);
});
it('its 100% for 10 conversion and 10 visits', () => {
const result = computeConversionRate(10, 10);
assert.strictEqual(result, 100);
});
it('its 0% for 0 conversion and 10 visits', () => {
const result = computeConversionRate(0, 10);
assert.strictEqual(result, 0);
});
it('its 100% for 1 conversion and 0 visits', () => {
const result = computeConversionRate(1, 0);
assert.strictEqual(result, 100);
});
it('its 0% for 0 conversion and 0 visits', () => {
const result = computeConversionRate(0, 0);
assert.strictEqual(result, 100);
});
it('its 100% for 2 conversion and 1 visits', () => {
const result = computeConversionRate(0, 0);
assert.strictEqual(result, 100);
});
});
15 changes: 15 additions & 0 deletions tools/rum/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,18 @@ export function parseConversionSpec() {
const filter = ([key]) => (key.startsWith('conversion.'));
return parseSearchParams(params, filter, transform);
}

/**
* Conversion rates are computed as the ratio of conversions to visits. The conversion rate is
* capped at 100%.
* @param conversions the number of conversions
* @param visits the number of visits
* @returns {number} the conversion rate as a percentage
*/
export function computeConversionRate(conversions, visits) {
const conversionRate = (100 * conversions) / visits;
if (conversionRate >= 0 && conversionRate <= 100) {
return conversionRate;
}
return 100;
}
Loading