From 3a2a47bc687b85b184ea993e79403827f37e3023 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 16 Apr 2026 14:18:28 -0700 Subject: [PATCH 1/3] feat(optel): add time-to-submit facet for form submission tracking Ports the time-to-submit feature from helix-website: adds formatTimeToMMSS and timeToSubmit helpers, a formsubmit.time facet, and a histogram facet (formsubmit.histogram) showing submission time distribution in mm:ss buckets. Also fixes malformed help-link HTML in explorer.html and adds numeric sorting for histogram facets in list-facet.js. Co-Authored-By: Claude Sonnet 4.6 (1M context) Co-authored-by: vdua --- tools/optel/oversight/elements/list-facet.js | 7 ++++ tools/optel/oversight/explorer.html | 23 +++++++----- tools/optel/oversight/slicer.js | 37 ++++++++++++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/tools/optel/oversight/elements/list-facet.js b/tools/optel/oversight/elements/list-facet.js index be1cae51..90d3bbdd 100644 --- a/tools/optel/oversight/elements/list-facet.js +++ b/tools/optel/oversight/elements/list-facet.js @@ -115,6 +115,13 @@ export default class ListFacet extends HTMLElement { if (sortMetric === 'count') return 0; // keep the default order from distiller if (sortMetric === 'weight') return b.weight - a.weight; // order by weight, aka number of page views if (sortMetric === 'value') return a.value.localeCompare(b.value); + if (sortMetric === 'asc') { + // numeric sort for histogram facets (e.g. "00:10 - 00:30") + const aNum = Number.parseFloat(a.value.replace(/[^0-9.]/g, '')); + const bNum = Number.parseFloat(b.value.replace(/[^0-9.]/g, '')); + if (Number.isNaN(aNum) || Number.isNaN(bNum)) return a.value.localeCompare(b.value); + return aNum - bNum; + } // get metric and property (e.g. lcp.mean) const aNum = a.metrics[sortMetric][sortProperty]; const bNum = b.metrics[sortMetric][sortProperty]; diff --git a/tools/optel/oversight/explorer.html b/tools/optel/oversight/explorer.html index 48b56c83..81957f74 100644 --- a/tools/optel/oversight/explorer.html +++ b/tools/optel/oversight/explorer.html @@ -462,20 +462,25 @@

TTFB

- + Form Submit Source (CSS Selector) + - + Form Submit Target + - + + Form Submit Time (mm:ss) + + + Form Field Source (CSS Selector) + diff --git a/tools/optel/oversight/slicer.js b/tools/optel/oversight/slicer.js index 3465eeb4..f4f53bc7 100644 --- a/tools/optel/oversight/slicer.js +++ b/tools/optel/oversight/slicer.js @@ -77,6 +77,28 @@ dataChunks.addSeries('timeOnPage', (bundle) => { return (deltas.reduce((a, b) => Math.max(a, b), -Infinity)) / 1000; }); +function formatTimeToMMSS(seconds) { + if (seconds === undefined || seconds === null || Number.isNaN(seconds)) { + return undefined; + } + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.floor(seconds % 60); + return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; +} + +function timeToSubmit(bundle) { + const filteredEvents = bundle.events.filter((evt) => evt.checkpoint === 'formsubmit' || evt.checkpoint === 'viewblock'); + const submitEvent = filteredEvents.find((evt) => evt.checkpoint === 'formsubmit'); + if (!submitEvent) { + return undefined; + } + const formViewEvent = filteredEvents.find((evt) => evt.checkpoint === 'viewblock' && evt.source === submitEvent.source); + if (!formViewEvent) { + return undefined; + } + return (submitEvent.timeDelta - formViewEvent.timeDelta) / 1000; +} + dataChunks.addSeries('contentEngagement', (bundle) => { const viewEvents = bundle.events .filter((evt) => evt.checkpoint === 'viewmedia' || evt.checkpoint === 'viewblock'); @@ -213,6 +235,8 @@ function updateDataFacets(filterText, params, checkpoint) { dataChunks.addFacet('checkpoint', facets.checkpoint, 'every', 'none'); + dataChunks.addFacet('formsubmit.time', timeToSubmit); + dataChunks.addFacet( 'conversions', (bundle) => (dataChunks.hasConversion(bundle, conversionSpec) ? 'converted' : 'not-converted'), @@ -285,6 +309,19 @@ function updateDataFacets(filterText, params, checkpoint) { ); } + if (cp === 'formsubmit') { + dataChunks.addHistogramFacet( + 'formsubmit.histogram', + 'formsubmit.time', + { + count: 10, min: 0, max: 600, steps: 'quantiles', + }, + { + format: formatTimeToMMSS, + }, + ); + } + // a bit of special handling here, so we can split the acquisition source if (cp === 'acquisition') { dataChunks.addFacet('acquisition.source', acquisitionSource); From 46c02129250fafa57a2c0d8e442cbe4d58881f90 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 16 Apr 2026 22:11:08 -0700 Subject: [PATCH 2/3] chore: update gitignore and move rum-distiller to runtime dependency Co-Authored-By: Claude Sonnet 4.6 (1M context) Co-authored-by: vdua --- .gitignore | 2 ++ package-lock.json | 3 +-- vendor/.vendor-hash | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 18c4b2bf..30bd87bb 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ MIGRATION_PLAN.md .playwright-mcp/ .claude/settings.local.json .claude/skills/tool-image-prompt/scripts/.cache/ + +.claude/worktrees diff --git a/package-lock.json b/package-lock.json index a592546e..e89a4d9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "hasInstallScript": true, "license": "Apache License 2.0", "dependencies": { + "@adobe/rum-distiller": "1.23.0", "yaml": "2.8.3" }, "devDependencies": { - "@adobe/rum-distiller": "1.23.0", "@babel/eslint-parser": "7.28.6", "esbuild": "0.28.0", "eslint": "8.57.1", @@ -27,7 +27,6 @@ "version": "1.23.0", "resolved": "https://registry.npmjs.org/@adobe/rum-distiller/-/rum-distiller-1.23.0.tgz", "integrity": "sha512-iHfI/Yme77Ayux8YjsVuFtRitgVp+XIrwnZbbQ3/w9LU32H6zCT3NlV5p4E/rZ8E3sS8IzC9cCCAL4vpavIQWg==", - "dev": true, "license": "Apache-2.0" }, "node_modules/@babel/code-frame": { diff --git a/vendor/.vendor-hash b/vendor/.vendor-hash index 4a705711..f59c281d 100644 --- a/vendor/.vendor-hash +++ b/vendor/.vendor-hash @@ -1 +1 @@ -4984b295ad9b28fa51d1e7cfc39213b0d215389a047c1f63adefbfdd1b364f97 \ No newline at end of file +75721f3b608641196bf5f95f3ea664d402cc7442e6df71bfe2d41c2a27b78d57 \ No newline at end of file From b6ff63251c1fa8dbf1aa14cb2dc889824065194e Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 16 Apr 2026 22:13:02 -0700 Subject: [PATCH 3/3] revert: restore package-lock.json and vendor hash to main state Co-Authored-By: Claude Sonnet 4.6 (1M context) Co-authored-by: vdua --- package-lock.json | 3 ++- vendor/.vendor-hash | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e89a4d9b..a592546e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "hasInstallScript": true, "license": "Apache License 2.0", "dependencies": { - "@adobe/rum-distiller": "1.23.0", "yaml": "2.8.3" }, "devDependencies": { + "@adobe/rum-distiller": "1.23.0", "@babel/eslint-parser": "7.28.6", "esbuild": "0.28.0", "eslint": "8.57.1", @@ -27,6 +27,7 @@ "version": "1.23.0", "resolved": "https://registry.npmjs.org/@adobe/rum-distiller/-/rum-distiller-1.23.0.tgz", "integrity": "sha512-iHfI/Yme77Ayux8YjsVuFtRitgVp+XIrwnZbbQ3/w9LU32H6zCT3NlV5p4E/rZ8E3sS8IzC9cCCAL4vpavIQWg==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@babel/code-frame": { diff --git a/vendor/.vendor-hash b/vendor/.vendor-hash index f59c281d..4a705711 100644 --- a/vendor/.vendor-hash +++ b/vendor/.vendor-hash @@ -1 +1 @@ -75721f3b608641196bf5f95f3ea664d402cc7442e6df71bfe2d41c2a27b78d57 \ No newline at end of file +4984b295ad9b28fa51d1e7cfc39213b0d215389a047c1f63adefbfdd1b364f97 \ No newline at end of file