From c984e4a455f89cc2131987938031e9dd66d34419 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Mon, 13 May 2024 11:57:23 -0700 Subject: [PATCH 01/15] Initial ubi integration --- _layouts/default.html | 20 ++- _layouts/home.html | 14 +- assets/js/search.js | 117 ++++++++++++++++- assets/js/timeme.min.js | 2 + assets/js/ubi.js | 276 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 424 insertions(+), 5 deletions(-) create mode 100644 assets/js/timeme.min.js create mode 100644 assets/js/ubi.js diff --git a/_layouts/default.html b/_layouts/default.html index 8ba6bd4703..40ddf734c6 100755 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -254,8 +254,26 @@

Related articles

anchors.add().remove('.subfooter h1, .subfooter h2'); {% endif %} + + + + + {% if site.search_enabled == false and site.use_custom_search == true %} - + {% endif %} diff --git a/_layouts/home.html b/_layouts/home.html index 0b13e44e23..92393aca5d 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -18,7 +18,6 @@ {% include header.html %} -
{{ content }} @@ -30,7 +29,18 @@
{% include footer.html %} - + + + + + diff --git a/assets/js/search.js b/assets/js/search.js index 37de270ebd..1d014f44ad 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -1,5 +1,9 @@ +import * as UBI from "./ubi.js"; + (() => { + document.addEventListener('DOMContentLoaded', () => { + UBI.initialize(); // // Search field behaviors // @@ -66,6 +70,10 @@ highlightResult(e.target?.closest('.top-banner-search--field-with-results--field--wrapper--search-component--search-results--result')); }, true); + elResults.addEventListener('click', e => { + clickResult(e.target?.closest('.top-banner-search--field-with-results--field--wrapper--search-component--search-results--result')); + }, true); + const debounceInput = () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(doSearch, 300); @@ -84,11 +92,14 @@ return sanitizeText(crumbs.join(' › ')); } + + const doSearch = async () => { const query = elInput.value.replace(/[^a-z0-9-_. ]+/ig, ' '); if (query.length < 3) return hideResults(true); if (query === lastQuery) return; + recordEvent('search', { search_term: query, docs_version: docsVersion @@ -97,6 +108,7 @@ lastQuery = query; abortPreviousCalls(); + UBI.clearCache(); elSpinner?.classList.add(CLASSNAME_SPINNING); if (!_showingResults) document.documentElement.classList.add('search-active'); @@ -118,10 +130,18 @@ if (!Array.isArray(data?.results) || data.results.length === 0) { return showNoResults(); } + const [qid, result_ids] = UBI.cacheQueryResults(data.results); + let ubi_event = makeUbiEvent('search', 'search_results', { + id:UBI.hash(query), + query:query, + result_ids:result_ids + }); + UBI.logEvent(ubi_event); + const chunks = data.results.map(result => result ? `
- + ${getBreadcrumbs(result)} ${sanitizeText(result.title || 'Unnamed Document')} @@ -206,6 +226,11 @@ const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; if (!node || !_showingResults || node.classList.contains(CLASSNAME_HIGHLIGHTED)) return; + const link = node.querySelector('a'); + if(link){ + //xxyy logUbiEvent('item_hover', link); + } + elResults.querySelectorAll(`.${searchResultClassName}.highlighted`).forEach(el => { el.classList.remove(CLASSNAME_HIGHLIGHTED); }); @@ -247,16 +272,104 @@ } }; + const clickResult = node => { + const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; + if (!node || !_showingResults) return; + + const link = node.querySelector('a'); + if(link){ + logUbiEvent('item_click', link); + } + + return true; + }; + const navToHighlightedResult = () => { const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; - elResults.querySelector(`.${searchResultClassName}.highlighted a[href]`)?.click?.(); + const link = elResults.querySelector(`.${searchResultClassName}.highlighted a[href]`); + if(link != null) + link.click?.(); }; + /** + * Find item and position clicked + * Modifies the ubi event data if the item is found + * @param {*} ubi_event + * @param {*} link + * @returns + */ + const setUbiClickData = (ubi_event, link) => { + ubi_event.event_attributes.position = new UBI.UbiPosition({x:link.offsetLeft, y:link.offsetTop}); + + if(link.hasAttribute('id')){ + let id = link.id; + //try to find the item ordinal within the result list + let result_ids = sessionStorage.getItem('result_ids'); + if(result_ids != null && result_ids.length > 0){ + result_ids = result_ids.split(','); + let ordinal = result_ids.findIndex(i=>i===id); + //if found, ordinal starts at 1 + if(ordinal != -1){ + ubi_event.event_attributes.position.ordinal = ordinal + 1; + if(ubi_event.message == undefined || ubi_event.message == null){ + ubi_event.message = `Clicked item ${ordinal+1} out of ${result_ids.length}` + } + + try{ + let search_results = JSON.parse(sessionStorage.getItem('search_results')); + let obj = search_results[id]; + if(obj != null){ + ubi_event.event_attributes.object = obj; + ubi_event.event_attributes.position.trail = getBreadcrumbs(obj); + } + }catch(e){ + console.warn(e); + } + } + } + } + return ubi_event; + }; + + + const makeUbiEvent = (name, event_type, data) => { + let e = new UBI.UbiEvent(name); + if(name == 'search'){ + e.message_type = 'QUERY'; + e.message = data.search_term; + e.event_attributes.object = data; + } else if(name == 'item_click') { + e = setUbiClickData(e, data); + } else { + switch(event_type) { + case "event1": + break; + case "event2": + break; + default:{ + if(e.event_attributes.object == null) + e.event_attributes.object = data; + } + } + } + return e; + } + + + const logUbiEvent = (name, data) => { + let event = makeUbiEvent(name, 'default', data) + UBI.logEvent(event); + }; + + const recordEvent = (name, data) => { try { gtag?.('event', name, data); + logUbiEvent(name, data); } catch (e) { // Do nothing + //xxyy + console.warn(e); } }; }); diff --git a/assets/js/timeme.min.js b/assets/js/timeme.min.js new file mode 100644 index 0000000000..7574bc81b3 --- /dev/null +++ b/assets/js/timeme.min.js @@ -0,0 +1,2 @@ +// from https://github.com/jasonzissman/TimeMe.js/blob/master/timeme.js +(()=>{((a,b)=>{if("undefined"!=typeof module&&module.exports)return module.exports=b();return"function"==typeof define&&define.amd?void define([],()=>a.TimeMe=b()):a.TimeMe=b()})(this,()=>{let a={startStopTimes:{},idleTimeoutMs:30000,currentIdleTimeMs:0,checkIdleStateRateMs:250,isUserCurrentlyOnPage:!0,isUserCurrentlyIdle:!1,currentPageName:"default-page-name",timeElapsedCallbacks:[],userLeftCallbacks:[],userReturnCallbacks:[],trackTimeOnElement:b=>{let c=document.getElementById(b);c&&(c.addEventListener("mouseover",()=>{a.startTimer(b)}),c.addEventListener("mousemove",()=>{a.startTimer(b)}),c.addEventListener("mouseleave",()=>{a.stopTimer(b)}),c.addEventListener("keypress",()=>{a.startTimer(b)}),c.addEventListener("focus",()=>{a.startTimer(b)}))},getTimeOnElementInSeconds:b=>{let c=a.getTimeOnPageInSeconds(b);return c?c:0},startTimer:(b,c)=>{if(b||(b=a.currentPageName),void 0===a.startStopTimes[b])a.startStopTimes[b]=[];else{let c=a.startStopTimes[b],d=c[c.length-1];if(void 0!==d&&void 0===d.stopTime)return}a.startStopTimes[b].push({startTime:c||new Date,stopTime:void 0})},stopAllTimers:()=>{let b=Object.keys(a.startStopTimes);for(let c=0;c{b||(b=a.currentPageName);let d=a.startStopTimes[b];void 0===d||0===d.length||d[d.length-1].stopTime===void 0&&(d[d.length-1].stopTime=c||new Date)},getTimeOnCurrentPageInSeconds:()=>a.getTimeOnPageInSeconds(a.currentPageName),getTimeOnPageInSeconds:b=>{let c=a.getTimeOnPageInMilliseconds(b);return void 0===c?void 0:c/1e3},getTimeOnCurrentPageInMilliseconds:()=>a.getTimeOnPageInMilliseconds(a.currentPageName),getTimeOnPageInMilliseconds:b=>{let c=0,d=a.startStopTimes[b];if(void 0===d)return;let e=0;for(let a=0;a{let b=[],c=Object.keys(a.startStopTimes);for(let d=0;d{let c=parseFloat(b);if(!1===isNaN(c))a.idleTimeoutMs=1e3*b;else throw{name:"InvalidDurationException",message:"An invalid duration time ("+b+") was provided."}},setCurrentPageName:b=>{a.currentPageName=b},resetRecordedPageTime:b=>{delete a.startStopTimes[b]},resetAllRecordedPageTimes:()=>{let b=Object.keys(a.startStopTimes);for(let c=0;c{a.isUserCurrentlyIdle&&a.triggerUserHasReturned(),a.resetIdleCountdown()},resetIdleCountdown:()=>{a.isUserCurrentlyIdle=!1,a.currentIdleTimeMs=0},callWhenUserLeaves:(b,c)=>{a.userLeftCallbacks.push({callback:b,numberOfTimesToInvoke:c})},callWhenUserReturns:(b,c)=>{a.userReturnCallbacks.push({callback:b,numberOfTimesToInvoke:c})},triggerUserHasReturned:()=>{if(!a.isUserCurrentlyOnPage){a.isUserCurrentlyOnPage=!0,a.resetIdleCountdown();for(let b=0;b{if(a.isUserCurrentlyOnPage){a.isUserCurrentlyOnPage=!1;for(let b=0;b{a.timeElapsedCallbacks.push({timeInSeconds:b,callback:c,pending:!0})},checkIdleState:()=>{for(let b=0;ba.timeElapsedCallbacks[b].timeInSeconds&&(a.timeElapsedCallbacks[b].callback(),a.timeElapsedCallbacks[b].pending=!1);!1===a.isUserCurrentlyIdle&&a.currentIdleTimeMs>a.idleTimeoutMs?(a.isUserCurrentlyIdle=!0,a.triggerUserHasLeftPageOrGoneIdle()):a.currentIdleTimeMs+=a.checkIdleStateRateMs},visibilityChangeEventName:void 0,hiddenPropName:void 0,listenForVisibilityEvents:(b,c)=>{b&&a.listenForUserLeavesOrReturnsEvents(),c&&a.listForIdleEvents()},listenForUserLeavesOrReturnsEvents:()=>{"undefined"==typeof document.hidden?"undefined"==typeof document.mozHidden?"undefined"==typeof document.msHidden?"undefined"!=typeof document.webkitHidden&&(a.hiddenPropName="webkitHidden",a.visibilityChangeEventName="webkitvisibilitychange"):(a.hiddenPropName="msHidden",a.visibilityChangeEventName="msvisibilitychange"):(a.hiddenPropName="mozHidden",a.visibilityChangeEventName="mozvisibilitychange"):(a.hiddenPropName="hidden",a.visibilityChangeEventName="visibilitychange"),document.addEventListener(a.visibilityChangeEventName,()=>{document[a.hiddenPropName]?a.triggerUserHasLeftPageOrGoneIdle():a.triggerUserHasReturned()},!1),window.addEventListener("blur",()=>{a.triggerUserHasLeftPageOrGoneIdle()}),window.addEventListener("focus",()=>{a.triggerUserHasReturned()})},listForIdleEvents:()=>{document.addEventListener("mousemove",()=>{a.userActivityDetected()}),document.addEventListener("keyup",()=>{a.userActivityDetected()}),document.addEventListener("touchstart",()=>{a.userActivityDetected()}),window.addEventListener("scroll",()=>{a.userActivityDetected()}),setInterval(()=>{!0!==a.isUserCurrentlyIdle&&a.checkIdleState()},a.checkIdleStateRateMs)},websocket:void 0,websocketHost:void 0,setUpWebsocket:b=>{if(window.WebSocket&&b){let c=b.websocketHost;try{a.websocket=new WebSocket(c),window.onbeforeunload=()=>{a.sendCurrentTime(b.appId)},a.websocket.onopen=()=>{a.sendInitWsRequest(b.appId)},a.websocket.onerror=a=>{console&&console.log("Error occurred in websocket connection: "+a)},a.websocket.onmessage=a=>{console&&console.log(a.data)}}catch(a){console&&console.error("Failed to connect to websocket host. Error:"+a)}}},websocketSend:b=>{a.websocket.send(JSON.stringify(b))},sendCurrentTime:b=>{let c=a.getTimeOnCurrentPageInMilliseconds(),d={type:"INSERT_TIME",appId:b,timeOnPageMs:c,pageName:a.currentPageName};a.websocketSend(d)},sendInitWsRequest:b=>{a.websocketSend({type:"INIT",appId:b})},initialize:b=>{let c,d,e=a.idleTimeoutMs||30,f=a.currentPageName||"default-page-name",g=!0,h=!0;b&&(e=b.idleTimeoutInSeconds||e,f=b.currentPageName||f,c=b.websocketOptions,d=b.initialStartTime,!1===b.trackWhenUserLeavesPage&&(g=!1),!1===b.trackWhenUserGoesIdle&&(h=!1)),a.setIdleDurationInSeconds(e),a.setCurrentPageName(f),a.setUpWebsocket(c),a.listenForVisibilityEvents(g,h),a.startTimer(void 0,d)}};return a})}).call(this); \ No newline at end of file diff --git a/assets/js/ubi.js b/assets/js/ubi.js new file mode 100644 index 0000000000..746ccc89c4 --- /dev/null +++ b/assets/js/ubi.js @@ -0,0 +1,276 @@ + + + + +function guiid() { + let id = '123456-insecure'; + try{ + id = crypto.randomUUID(); + } + catch(error){ + console.warn('tried to generate a guiid in an insecure context'); + id ='10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); + } + return id; + }; + + export function hash(str, seed=42) { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for(let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); + + return 4294967296 * (2097151 & h2) + (h1 >>> 0); +}; + + +/** + * In place of true authentication, this makes a hash out of the user's IP address and browser + * for tracking individual user behavior + * user_id = hash( user ip ) + '::' + hash( userAgent ) + * + * NOTE: if this function is called, but user_id starts with 'USER-', + * the function below did not complete successfully, + * and userError() was called instead + * @returns + */ +export function initialize(){ + let i = 1; + /* + let data = { + "search_term": "anything", + "docs_version": "latest" + }; + let payload = new UbiEvent('earch', {data_object:data}); + console.log(payload.toJson()); + */ + try{ + if(!sessionStorage.hasOwnProperty('session_id')) + sessionStorage.setItem('session_id', guiid()); + + if(sessionStorage.hasOwnProperty('user_id')){ + console.log('Already initialized UBI'); + return; + } + var rq = new XMLHttpRequest; + + rq.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + if(this.response != null) { + + //make a new user id: user ip + '::' + hash( userAgent ) + let client_id = ''; + if( window.navigator != null && window.navigator.userAgent != null){ + client_id = window.navigator.userAgent; + } else { + client_id = guiid(); + } + let user_id = hash( this.response.ip ) + '::' + hash( client_id ); + sessionStorage.setItem('user_id', user_id); + console.log('user_id: ' + user_id); + + } + } + }; + + rq.onerror = function(){ + userError(); + if(this.error != null && this.error != ''){ + console.error('ERROR Retrieving user info: ' + this.error); + } + else + console.error('UNSPECIFIED ERROR Retrieving user info'); + } + + rq.open("GET", "https://api64.ipify.org?format=json", true); + rq.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + rq.responseType = "json"; + rq.send(); + + } catch(error){ + console.log(error) + } + +} + /** + * back up method to make a user individual + * @returns + */ +function userError(){ + let user_id = 'USER-' + guiid(); + sessionStorage.setItem('user_id', user_id); + return user_id; +} + + +export function genQueryId(){ + const qid = 'QUERY-' + guiid(); + sessionStorage.setItem('query_id', qid); + return qid; +} +export function getQueryId(){ + return sessionStorage.getItem('query_id'); +} + +/** + * Save explicitly, if conditions are right + */ +export function saveQueryId(query_id){ + sessionStorage.setItem('query_id', query_id); +} + +export function clearCache() { + sessionStorage.removeItem('search_results'); + sessionStorage.removeItem('result_ids'); +} + +export function cacheQueryResults(results){ + let qid = genQueryId(); + saveQueryId(qid); + + if(results.length > 0){ + let search_results = {}; + for(var res of results){ + if(!res.hasOwnProperty('id')){ + res.id = hash(res.url) + } + search_results[res.id] = res; + } + let result_ids = Object.keys(search_results); + sessionStorage.setItem('search_results', JSON.stringify(search_results)); + sessionStorage.setItem('result_ids', result_ids); + return [qid, result_ids]; + } + return [qid, []]; +} + +export function getUserId(){ + if(sessionStorage.hasOwnProperty('user_id')){ + return sessionStorage.getItem('user_id'); + } + + return userError(); +} + +export function getSessionId(){ + if(sessionStorage.hasOwnProperty('session_id')){ + return sessionStorage.getItem('session_id'); + } + + let session_id = guiid(); + sessionStorage.setItem('session_id', session_id); + return session_id; +} + +export function getPageId(){ + return location.pathname; + +} + +export async function logEvent(event){ + try { + fetch('http://localhost:9200/ubi/docs', { + method: 'POST', + headers: { + 'Accept': 'application/json, text/plain, */*', + 'Content-Type': 'application/json' + }, + body: event.toJson() + }).then(res => res.json()) + .then(res => console.log(res)) + .catch((error) => { + console.log(error) + }); + } catch (e) { + console.warn('Ubi error: ' + JSON.stringify(e)); + } + +} +/** + * Ubi Event data structures + */ + +export class UbiEventData { + constructor(type, id=null, description=null, details=null) { + this.object_type = type; + this.object_id = id; + this.description = description; + this.object_detail = details; + + //override if using key_field's and values + this.key_value = id; + } +} +export class UbiPosition{ + constructor({ordinal=null, x=null, y=null, trail=null}={}) { + this.ordinal = ordinal; + this.x = x; + this.y = y; + this.trail = trail; + } +} + + +export class UbiEventAttributes { + /** + * Attributes, other than `object` or `position` should be in the form of + * attributes['item1'] = 1 + * attributes['item2'] = '2' + * + * The object member is reserved for further, relevant object payloads or classes + */ + constructor({attributes={}, object=null, position=null}={}) { + this.object = object; + this.position = position; + for(var entry in Object.entries(attributes)){ + this[entry.key] = entry.value; + } + } +} + + + +export class UbiEvent { + constructor(action_name, {message=null, attributes={}, data_object={}}={}) { + this.action_name = action_name; + this.user_id = getUserId(); + this.query_id = getQueryId(); + this.session_id = getSessionId(); + this.page_id = getPageId(); + this.timestamp = Date.now(); + + this.message_type = 'INFO'; + if( message ) + this.message = message; + + this.event_attributes = new UbiEventAttributes({attributes:attributes}); + } + + /** + * Use to suppress null objects in the json output + * @param key + * @param value + * @returns + */ + static replacer(key, value){ + if(value == null) + return undefined; + return value; + } + + /** + * + * @returns json string + */ + toJson() { + return JSON.stringify(this, UbiEvent.replacer); + } +} \ No newline at end of file From 0dafc6b355dbc6344b9345089d845697f19dbe71 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Mon, 13 May 2024 13:37:48 -0700 Subject: [PATCH 02/15] dwell time integration --- assets/js/home-listener.js | 8 +++++++ assets/js/listener.js | 8 +++++++ assets/js/search.js | 47 +++++++++++++++++++------------------- assets/js/ubi.js | 3 ++- 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/assets/js/home-listener.js b/assets/js/home-listener.js index 160414f132..610222b948 100644 --- a/assets/js/home-listener.js +++ b/assets/js/home-listener.js @@ -2,4 +2,12 @@ const contributeButton = document.getElementById('contribute'); contributeButton.addEventListener('click', function(event) { window.open('https://github.com/opensearch-project', '_blank'); +}); + +window.addEventListener("DOMContentLoaded", function (e) { + TimeMe.startTimer(window.location.pathname); +}); + +window.addEventListener("beforeunload", function (e) { + TimeMe.stopTimer(window.location.pathname); }); \ No newline at end of file diff --git a/assets/js/listener.js b/assets/js/listener.js index 029e042419..2ab4c43fce 100644 --- a/assets/js/listener.js +++ b/assets/js/listener.js @@ -8,6 +8,14 @@ const nav = document.getElementById('site-nav'); const versionPanel = document.getElementById('version-panel'); document.addEventListener('DOMContentLoaded', updateTextArea); +window.addEventListener("DOMContentLoaded", function (e) { + TimeMe.startTimer(window.location.pathname); +}); + +window.addEventListener("beforeunload", function (e) { + TimeMe.stopTimer(window.location.pathname); +}); + document.addEventListener('click', function(event) { const { target } = event; if (target.matches('.feedback-issue')) { diff --git a/assets/js/search.js b/assets/js/search.js index 1d014f44ad..1fcb3a57f5 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -3,7 +3,9 @@ import * as UBI from "./ubi.js"; (() => { document.addEventListener('DOMContentLoaded', () => { + UBI.initialize(); + TimeMe.startTimer( window.location.pathname ); // // Search field behaviors // @@ -92,14 +94,11 @@ import * as UBI from "./ubi.js"; return sanitizeText(crumbs.join(' › ')); } - - const doSearch = async () => { const query = elInput.value.replace(/[^a-z0-9-_. ]+/ig, ' '); if (query.length < 3) return hideResults(true); if (query === lastQuery) return; - recordEvent('search', { search_term: query, docs_version: docsVersion @@ -286,41 +285,40 @@ import * as UBI from "./ubi.js"; const navToHighlightedResult = () => { const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; - const link = elResults.querySelector(`.${searchResultClassName}.highlighted a[href]`); - if(link != null) - link.click?.(); + elResults.querySelector(`.${searchResultClassName}.highlighted a[href]`)?.click?.(); }; /** * Find item and position clicked * Modifies the ubi event data if the item is found - * @param {*} ubi_event + * @param {*} ubiEvent * @param {*} link * @returns */ - const setUbiClickData = (ubi_event, link) => { - ubi_event.event_attributes.position = new UBI.UbiPosition({x:link.offsetLeft, y:link.offsetTop}); + const setUbiClickData = (ubiEvent, link) => { + ubiEvent.event_attributes.position = new UBI.UbiPosition({x:link.offsetLeft, y:link.offsetTop}); + if(link.hasAttribute('id')){ let id = link.id; //try to find the item ordinal within the result list - let result_ids = sessionStorage.getItem('result_ids'); - if(result_ids != null && result_ids.length > 0){ - result_ids = result_ids.split(','); - let ordinal = result_ids.findIndex(i=>i===id); + let resultIds = sessionStorage.getItem('result_ids'); + if(resultIds != null && resultIds.length > 0){ + resultIds = resultIds.split(','); + let ordinal = resultIds.findIndex( i => i===id ); //if found, ordinal starts at 1 if(ordinal != -1){ - ubi_event.event_attributes.position.ordinal = ordinal + 1; - if(ubi_event.message == undefined || ubi_event.message == null){ - ubi_event.message = `Clicked item ${ordinal+1} out of ${result_ids.length}` + ubiEvent.event_attributes.position.ordinal = ordinal + 1; + if(ubiEvent.message == undefined || ubi_event.message == null){ + ubiEvent.message = `Clicked item ${ordinal+1} out of ${result_ids.length}` } try{ - let search_results = JSON.parse(sessionStorage.getItem('search_results')); - let obj = search_results[id]; + let searchResults = JSON.parse(sessionStorage.getItem('search_results')); + let obj = searchResults[id]; if(obj != null){ - ubi_event.event_attributes.object = obj; - ubi_event.event_attributes.position.trail = getBreadcrumbs(obj); + ubiEvent.event_attributes.object = obj; + ubiEvent.event_attributes.position.trail = getBreadcrumbs(obj); } }catch(e){ console.warn(e); @@ -328,12 +326,17 @@ import * as UBI from "./ubi.js"; } } } - return ubi_event; + return ubiEvent; }; const makeUbiEvent = (name, event_type, data) => { let e = new UBI.UbiEvent(name); + let t = TimeMe.getTimeOnPageInSeconds(window.location.pathname); + if(t != null){ + e.event_attributes['dwell_seconds'] = t; + } + if(name == 'search'){ e.message_type = 'QUERY'; e.message = data.search_term; @@ -368,8 +371,6 @@ import * as UBI from "./ubi.js"; logUbiEvent(name, data); } catch (e) { // Do nothing - //xxyy - console.warn(e); } }; }); diff --git a/assets/js/ubi.js b/assets/js/ubi.js index 746ccc89c4..536ba163c1 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -42,7 +42,7 @@ function guiid() { * and userError() was called instead * @returns */ -export function initialize(){ +export async function initialize(){ let i = 1; /* let data = { @@ -177,6 +177,7 @@ export function getPageId(){ export async function logEvent(event){ try { + //=>146.190.147.150 fetch('http://localhost:9200/ubi/docs', { method: 'POST', headers: { From eaf100f4c6e131deb2c955344b58e9609b328898 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Tue, 14 May 2024 08:35:11 -0700 Subject: [PATCH 03/15] finalizing dwell time events --- assets/js/home-listener.js | 8 ------ assets/js/listener.js | 8 ------ assets/js/search.js | 6 +--- assets/js/ubi.js | 57 +++++++++++++++++++++++++++----------- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/assets/js/home-listener.js b/assets/js/home-listener.js index 610222b948..160414f132 100644 --- a/assets/js/home-listener.js +++ b/assets/js/home-listener.js @@ -2,12 +2,4 @@ const contributeButton = document.getElementById('contribute'); contributeButton.addEventListener('click', function(event) { window.open('https://github.com/opensearch-project', '_blank'); -}); - -window.addEventListener("DOMContentLoaded", function (e) { - TimeMe.startTimer(window.location.pathname); -}); - -window.addEventListener("beforeunload", function (e) { - TimeMe.stopTimer(window.location.pathname); }); \ No newline at end of file diff --git a/assets/js/listener.js b/assets/js/listener.js index 2ab4c43fce..029e042419 100644 --- a/assets/js/listener.js +++ b/assets/js/listener.js @@ -8,14 +8,6 @@ const nav = document.getElementById('site-nav'); const versionPanel = document.getElementById('version-panel'); document.addEventListener('DOMContentLoaded', updateTextArea); -window.addEventListener("DOMContentLoaded", function (e) { - TimeMe.startTimer(window.location.pathname); -}); - -window.addEventListener("beforeunload", function (e) { - TimeMe.stopTimer(window.location.pathname); -}); - document.addEventListener('click', function(event) { const { target } = event; if (target.matches('.feedback-issue')) { diff --git a/assets/js/search.js b/assets/js/search.js index 1fcb3a57f5..99c13fcf3d 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -3,9 +3,6 @@ import * as UBI from "./ubi.js"; (() => { document.addEventListener('DOMContentLoaded', () => { - - UBI.initialize(); - TimeMe.startTimer( window.location.pathname ); // // Search field behaviors // @@ -298,7 +295,6 @@ import * as UBI from "./ubi.js"; const setUbiClickData = (ubiEvent, link) => { ubiEvent.event_attributes.position = new UBI.UbiPosition({x:link.offsetLeft, y:link.offsetTop}); - if(link.hasAttribute('id')){ let id = link.id; //try to find the item ordinal within the result list @@ -310,7 +306,7 @@ import * as UBI from "./ubi.js"; if(ordinal != -1){ ubiEvent.event_attributes.position.ordinal = ordinal + 1; if(ubiEvent.message == undefined || ubi_event.message == null){ - ubiEvent.message = `Clicked item ${ordinal+1} out of ${result_ids.length}` + ubiEvent.message = `Clicked item ${ordinal+1} out of ${resultIds.length}` } try{ diff --git a/assets/js/ubi.js b/assets/js/ubi.js index 536ba163c1..d0eaaee8ce 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -44,14 +44,7 @@ function guiid() { */ export async function initialize(){ let i = 1; - /* - let data = { - "search_term": "anything", - "docs_version": "latest" - }; - let payload = new UbiEvent('earch', {data_object:data}); - console.log(payload.toJson()); - */ + try{ if(!sessionStorage.hasOwnProperty('session_id')) sessionStorage.setItem('session_id', guiid()); @@ -172,7 +165,35 @@ export function getSessionId(){ export function getPageId(){ return location.pathname; +} +window.addEventListener("DOMContentLoaded", function (e) { + try{ + initialize(); + TimeMe.currentPageName = this.window.location.href; + TimeMe.startTimer(window.location.pathname); + }catch(e){ + + } +}); + +window.addEventListener("beforeunload", function (e) { + try{ + TimeMe.stopTimer(window.location.pathname); + logDwellTime('page_exit', window.location.pathname, + TimeMe.getTimeOnPageInSeconds(window.location.pathname)); + }catch(e){ + } +}); + +export async function logDwellTime(action_name, page, seconds){ + console.log(`${page} => ${seconds}`); + let e = new UbiEvent(action_name, { + message:`On page ${page} for ${seconds} seconds`, + event_attributes:{dwell_seconds:seconds}, + data_object:TimeMe + }); + logEvent(e); } export async function logEvent(event){ @@ -195,9 +216,11 @@ export async function logEvent(event){ } } -/** + +/********************************************************************************************* * Ubi Event data structures - */ + * The following structures help ensure adherence to the UBI event schema + *********************************************************************************************/ export class UbiEventData { constructor(type, id=null, description=null, details=null) { @@ -229,18 +252,20 @@ export class UbiEventAttributes { * The object member is reserved for further, relevant object payloads or classes */ constructor({attributes={}, object=null, position=null}={}) { - this.object = object; - this.position = position; - for(var entry in Object.entries(attributes)){ - this[entry.key] = entry.value; + if(attributes != null){ + Object.assign(this, attributes); + } + if(object != null && object != {}){ + this.object = object; } + this.position = position; } } export class UbiEvent { - constructor(action_name, {message=null, attributes={}, data_object={}}={}) { + constructor(action_name, {message=null, event_attributes={}, data_object={}}={}) { this.action_name = action_name; this.user_id = getUserId(); this.query_id = getQueryId(); @@ -252,7 +277,7 @@ export class UbiEvent { if( message ) this.message = message; - this.event_attributes = new UbiEventAttributes({attributes:attributes}); + this.event_attributes = new UbiEventAttributes({attributes:event_attributes, object:data_object}); } /** From 3d297a1553c223f15b1bea66736c749c3d0ea1e7 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 May 2024 12:36:19 -0400 Subject: [PATCH 04/15] Remove tabs --- _layouts/default.html | 8 ++++---- _layouts/home.html | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 40ddf734c6..a7ec30f0a8 100755 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -258,16 +258,16 @@

Related articles

diff --git a/_layouts/home.html b/_layouts/home.html index 92393aca5d..9ce7b62b1e 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -33,8 +33,8 @@ From de75236f3157eb1d95b901c6ac54918c1ed80714 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 May 2024 12:47:07 -0400 Subject: [PATCH 05/15] Whitespace and untab treatment --- assets/js/ubi.js | 460 +++++++++++++++++++++++------------------------ 1 file changed, 229 insertions(+), 231 deletions(-) diff --git a/assets/js/ubi.js b/assets/js/ubi.js index d0eaaee8ce..c4e82527c6 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -1,220 +1,217 @@ - - - - function guiid() { - let id = '123456-insecure'; - try{ - id = crypto.randomUUID(); - } - catch(error){ - console.warn('tried to generate a guiid in an insecure context'); - id ='10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => - (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) - ); - } - return id; - }; - - export function hash(str, seed=42) { - let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; - for(let i = 0, ch; i < str.length; i++) { - ch = str.charCodeAt(i); - h1 = Math.imul(h1 ^ ch, 2654435761); - h2 = Math.imul(h2 ^ ch, 1597334677); - } - h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); - h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); - h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); - h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); - - return 4294967296 * (2097151 & h2) + (h1 >>> 0); + let id = '123456-insecure'; + try { + id = crypto.randomUUID(); + } + catch(error){ + console.warn('tried to generate a guiid in an insecure context'); + id ='10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); + } + return id; +}; + +export function hash(str, seed=42) { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for(let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); + + return 4294967296 * (2097151 & h2) + (h1 >>> 0); }; /** * In place of true authentication, this makes a hash out of the user's IP address and browser * for tracking individual user behavior - * user_id = hash( user ip ) + '::' + hash( userAgent ) - * - * NOTE: if this function is called, but user_id starts with 'USER-', - * the function below did not complete successfully, - * and userError() was called instead - * @returns + * user_id = hash( user ip ) + '::' + hash( userAgent ) + * + * NOTE: if this function is called, but user_id starts with 'USER-', + * the function below did not complete successfully, + * and userError() was called instead + * @returns */ export async function initialize(){ - let i = 1; - - try{ - if(!sessionStorage.hasOwnProperty('session_id')) - sessionStorage.setItem('session_id', guiid()); - - if(sessionStorage.hasOwnProperty('user_id')){ - console.log('Already initialized UBI'); - return; - } - var rq = new XMLHttpRequest; - - rq.onreadystatechange = function() { - if (this.readyState == 4 && this.status == 200) { - if(this.response != null) { - - //make a new user id: user ip + '::' + hash( userAgent ) - let client_id = ''; - if( window.navigator != null && window.navigator.userAgent != null){ - client_id = window.navigator.userAgent; - } else { - client_id = guiid(); - } - let user_id = hash( this.response.ip ) + '::' + hash( client_id ); - sessionStorage.setItem('user_id', user_id); - console.log('user_id: ' + user_id); - - } - } - }; - - rq.onerror = function(){ - userError(); - if(this.error != null && this.error != ''){ - console.error('ERROR Retrieving user info: ' + this.error); - } - else - console.error('UNSPECIFIED ERROR Retrieving user info'); - } - - rq.open("GET", "https://api64.ipify.org?format=json", true); - rq.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - rq.responseType = "json"; - rq.send(); - - } catch(error){ - console.log(error) - } + let i = 1; + try { + if(!sessionStorage.hasOwnProperty('session_id')) { + sessionStorage.setItem('session_id', guiid()); + } + + if(sessionStorage.hasOwnProperty('user_id')){ + console.log('Already initialized UBI'); + return; + } + + var rq = new XMLHttpRequest; + + rq.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + if(this.response != null) { + + //make a new user id: user ip + '::' + hash( userAgent ) + let client_id = ''; + if( window.navigator != null && window.navigator.userAgent != null){ + client_id = window.navigator.userAgent; + } else { + client_id = guiid(); + } + let user_id = hash( this.response.ip ) + '::' + hash( client_id ); + sessionStorage.setItem('user_id', user_id); + console.log('user_id: ' + user_id); + + } + } + }; + + rq.onerror = function(){ + userError(); + if(this.error != null && this.error != ''){ + console.error('ERROR Retrieving user info: ' + this.error); + } + else + console.error('UNSPECIFIED ERROR Retrieving user info'); + } + + rq.open("GET", "https://api64.ipify.org?format=json", true); + rq.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + rq.responseType = "json"; + rq.send(); + + } catch(error){ + console.log(error) + } } - /** - * back up method to make a user individual - * @returns - */ + +/** +* back up method to make a user individual +* @returns +*/ function userError(){ - let user_id = 'USER-' + guiid(); - sessionStorage.setItem('user_id', user_id); - return user_id; -} + let user_id = 'USER-' + guiid(); + sessionStorage.setItem('user_id', user_id); + return user_id; +} export function genQueryId(){ - const qid = 'QUERY-' + guiid(); - sessionStorage.setItem('query_id', qid); - return qid; + const qid = 'QUERY-' + guiid(); + sessionStorage.setItem('query_id', qid); + return qid; } export function getQueryId(){ - return sessionStorage.getItem('query_id'); + return sessionStorage.getItem('query_id'); } /** * Save explicitly, if conditions are right */ export function saveQueryId(query_id){ - sessionStorage.setItem('query_id', query_id); + sessionStorage.setItem('query_id', query_id); } export function clearCache() { - sessionStorage.removeItem('search_results'); - sessionStorage.removeItem('result_ids'); + sessionStorage.removeItem('search_results'); + sessionStorage.removeItem('result_ids'); } export function cacheQueryResults(results){ - let qid = genQueryId(); - saveQueryId(qid); - - if(results.length > 0){ - let search_results = {}; - for(var res of results){ - if(!res.hasOwnProperty('id')){ - res.id = hash(res.url) - } - search_results[res.id] = res; - } - let result_ids = Object.keys(search_results); - sessionStorage.setItem('search_results', JSON.stringify(search_results)); - sessionStorage.setItem('result_ids', result_ids); - return [qid, result_ids]; - } - return [qid, []]; + let qid = genQueryId(); + saveQueryId(qid); + + if(results.length > 0){ + let search_results = {}; + for(var res of results){ + if(!res.hasOwnProperty('id')){ + res.id = hash(res.url) + } + search_results[res.id] = res; + } + let result_ids = Object.keys(search_results); + sessionStorage.setItem('search_results', JSON.stringify(search_results)); + sessionStorage.setItem('result_ids', result_ids); + return [qid, result_ids]; + } + return [qid, []]; } export function getUserId(){ - if(sessionStorage.hasOwnProperty('user_id')){ - return sessionStorage.getItem('user_id'); - } + if(sessionStorage.hasOwnProperty('user_id')){ + return sessionStorage.getItem('user_id'); + } - return userError(); + return userError(); } export function getSessionId(){ - if(sessionStorage.hasOwnProperty('session_id')){ - return sessionStorage.getItem('session_id'); - } - - let session_id = guiid(); - sessionStorage.setItem('session_id', session_id); - return session_id; + if(sessionStorage.hasOwnProperty('session_id')){ + return sessionStorage.getItem('session_id'); + } + + let session_id = guiid(); + sessionStorage.setItem('session_id', session_id); + return session_id; } export function getPageId(){ - return location.pathname; + return location.pathname; } + window.addEventListener("DOMContentLoaded", function (e) { - try{ - initialize(); - TimeMe.currentPageName = this.window.location.href; - TimeMe.startTimer(window.location.pathname); - }catch(e){ + try{ + initialize(); + TimeMe.currentPageName = this.window.location.href; + TimeMe.startTimer(window.location.pathname); + } catch(e){ - } + } }); window.addEventListener("beforeunload", function (e) { - try{ - TimeMe.stopTimer(window.location.pathname); - logDwellTime('page_exit', window.location.pathname, - TimeMe.getTimeOnPageInSeconds(window.location.pathname)); - }catch(e){ + try{ + TimeMe.stopTimer(window.location.pathname); + logDwellTime('page_exit', window.location.pathname, + TimeMe.getTimeOnPageInSeconds(window.location.pathname)); + } catch(e){ - } + } }); export async function logDwellTime(action_name, page, seconds){ - console.log(`${page} => ${seconds}`); - let e = new UbiEvent(action_name, { - message:`On page ${page} for ${seconds} seconds`, - event_attributes:{dwell_seconds:seconds}, - data_object:TimeMe - }); - logEvent(e); + console.log(`${page} => ${seconds}`); + let e = new UbiEvent(action_name, { + message:`On page ${page} for ${seconds} seconds`, + event_attributes:{dwell_seconds:seconds}, + data_object:TimeMe + }); + logEvent(e); } export async function logEvent(event){ - try { - //=>146.190.147.150 - fetch('http://localhost:9200/ubi/docs', { - method: 'POST', - headers: { - 'Accept': 'application/json, text/plain, */*', - 'Content-Type': 'application/json' - }, - body: event.toJson() - }).then(res => res.json()) - .then(res => console.log(res)) - .catch((error) => { - console.log(error) - }); - } catch (e) { - console.warn('Ubi error: ' + JSON.stringify(e)); - } - + try { + //=>146.190.147.150 + fetch('http://localhost:9200/ubi/docs', { + method: 'POST', + headers: { + 'Accept': 'application/json, text/plain, */*', + 'Content-Type': 'application/json' + }, + body: event.toJson() + }).then(res => res.json()) + .then(res => console.log(res)) + .catch((error) => { + console.log(error) + }); + } catch (e) { + console.warn('Ubi error: ' + JSON.stringify(e)); + } } /********************************************************************************************* @@ -223,80 +220,81 @@ export async function logEvent(event){ *********************************************************************************************/ export class UbiEventData { - constructor(type, id=null, description=null, details=null) { - this.object_type = type; - this.object_id = id; - this.description = description; - this.object_detail = details; - - //override if using key_field's and values - this.key_value = id; - } + constructor(type, id=null, description=null, details=null) { + this.object_type = type; + this.object_id = id; + this.description = description; + this.object_detail = details; + + //override if using key_field's and values + this.key_value = id; + } } export class UbiPosition{ - constructor({ordinal=null, x=null, y=null, trail=null}={}) { - this.ordinal = ordinal; - this.x = x; - this.y = y; - this.trail = trail; - } + constructor({ordinal=null, x=null, y=null, trail=null}={}) { + this.ordinal = ordinal; + this.x = x; + this.y = y; + this.trail = trail; + } } export class UbiEventAttributes { - /** - * Attributes, other than `object` or `position` should be in the form of - * attributes['item1'] = 1 - * attributes['item2'] = '2' - * - * The object member is reserved for further, relevant object payloads or classes - */ - constructor({attributes={}, object=null, position=null}={}) { - if(attributes != null){ - Object.assign(this, attributes); - } - if(object != null && object != {}){ - this.object = object; - } - this.position = position; - } + /** + * Attributes, other than `object` or `position` should be in the form of + * attributes['item1'] = 1 + * attributes['item2'] = '2' + * + * The object member is reserved for further, relevant object payloads or classes + */ + constructor({attributes={}, object=null, position=null}={}) { + if(attributes != null){ + Object.assign(this, attributes); + } + if(object != null && object != {}){ + this.object = object; + } + this.position = position; + } } export class UbiEvent { - constructor(action_name, {message=null, event_attributes={}, data_object={}}={}) { - this.action_name = action_name; - this.user_id = getUserId(); - this.query_id = getQueryId(); - this.session_id = getSessionId(); - this.page_id = getPageId(); - this.timestamp = Date.now(); - - this.message_type = 'INFO'; - if( message ) - this.message = message; - - this.event_attributes = new UbiEventAttributes({attributes:event_attributes, object:data_object}); - } - - /** - * Use to suppress null objects in the json output - * @param key - * @param value - * @returns - */ - static replacer(key, value){ - if(value == null) - return undefined; - return value; - } - - /** - * - * @returns json string - */ - toJson() { - return JSON.stringify(this, UbiEvent.replacer); - } -} \ No newline at end of file + constructor(action_name, {message=null, event_attributes={}, data_object={}}={}) { + this.action_name = action_name; + this.user_id = getUserId(); + this.query_id = getQueryId(); + this.session_id = getSessionId(); + this.page_id = getPageId(); + this.timestamp = Date.now(); + + this.message_type = 'INFO'; + if( message ) + this.message = message; + + this.event_attributes = new UbiEventAttributes({attributes:event_attributes, object:data_object}); + } + + /** + * Use to suppress null objects in the json output + * @param key + * @param value + * @returns + */ + static replacer(key, value){ + if(value == null) { + return undefined; + } + return value; + } + + /** + * + * @returns json string + */ + toJson() { + return JSON.stringify(this, UbiEvent.replacer); + } +} From 49c8f986aa39e645d1490b0619c3764dd9a14376 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Wed, 15 May 2024 16:20:44 -0700 Subject: [PATCH 06/15] Iterative updates, including user feedback events. --- _layouts/default.html | 18 +--------- assets/js/listener.js | 20 ++++++++++- assets/js/ubi.js | 80 ++++++++++++++++++------------------------- 3 files changed, 54 insertions(+), 64 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index a7ec30f0a8..48866a180c 100755 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -255,28 +255,12 @@

Related articles

{% endif %} - - - {% if site.search_enabled == false and site.use_custom_search == true %} {% endif %} - + diff --git a/assets/js/listener.js b/assets/js/listener.js index 029e042419..40ce701bc7 100644 --- a/assets/js/listener.js +++ b/assets/js/listener.js @@ -1,3 +1,5 @@ +import * as UBI from "./ubi.js"; + const yesButton = document.getElementById('yes'); const noButton = document.getElementById('no'); const numCharsLabel = document.getElementById('num-chars'); @@ -48,7 +50,7 @@ function updateTextArea() { } // calculate the number of characters remaining - counter = 350 - commentTextArea.value.length; + const counter = 350 - commentTextArea.value.length; numCharsLabel.innerText = counter + " characters left"; } @@ -68,6 +70,22 @@ function sendFeedback() { if (helpful === 'none' && comment === 'none') return; + try{ + let e = new UBI.UbiEvent('user_feedback', { + message: `Relevance: ${helpful}, Comment: ${comment}`, + event_attributes:{ + url:location.pathname, + helpful:helpful, + comment:comment + } + }); + e.message_type = 'USER'; + UBI.logEvent(e); + + } catch(e){ + console.warn(`UBI Error: ${e}`) + } + // split the comment into 100-char parts because of GA limitation on custom dimensions const commentLines = ["", "", "", ""]; for (let i = 0; i <= (comment.length - 1)/100; i++) { diff --git a/assets/js/ubi.js b/assets/js/ubi.js index c4e82527c6..4ea41b873f 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -1,3 +1,4 @@ + function guiid() { let id = '123456-insecure'; try { @@ -42,7 +43,7 @@ export async function initialize(){ try { if(!sessionStorage.hasOwnProperty('session_id')) { - sessionStorage.setItem('session_id', guiid()); + sessionStorage.setItem('session_id', 'S-' + guiid()); } if(sessionStorage.hasOwnProperty('user_id')){ @@ -50,40 +51,15 @@ export async function initialize(){ return; } - var rq = new XMLHttpRequest; - - rq.onreadystatechange = function() { - if (this.readyState == 4 && this.status == 200) { - if(this.response != null) { - - //make a new user id: user ip + '::' + hash( userAgent ) - let client_id = ''; - if( window.navigator != null && window.navigator.userAgent != null){ - client_id = window.navigator.userAgent; - } else { - client_id = guiid(); - } - let user_id = hash( this.response.ip ) + '::' + hash( client_id ); - sessionStorage.setItem('user_id', user_id); - console.log('user_id: ' + user_id); - - } - } - }; - - rq.onerror = function(){ + // currently, the only cookie is gtag's client_id et al. + if(document.cookie && document.cookie.length > 0){ + setUserId(hash(document.cookie)); + return; + } else { + //back up user_id method userError(); - if(this.error != null && this.error != ''){ - console.error('ERROR Retrieving user info: ' + this.error); - } - else - console.error('UNSPECIFIED ERROR Retrieving user info'); } - rq.open("GET", "https://api64.ipify.org?format=json", true); - rq.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - rq.responseType = "json"; - rq.send(); } catch(error){ console.log(error) @@ -91,18 +67,20 @@ export async function initialize(){ } /** -* back up method to make a user individual +* Back up method to make a user individual +* Note that this is basically the same as a session id since it would +* generate each time a user lands on the site * @returns */ function userError(){ - let user_id = 'USER-' + guiid(); - sessionStorage.setItem('user_id', user_id); + let user_id = guiid(); + setUserId(user_id); return user_id; } export function genQueryId(){ - const qid = 'QUERY-' + guiid(); + const qid = 'Q-' + guiid(); sessionStorage.setItem('query_id', qid); return qid; } @@ -113,7 +91,7 @@ export function getQueryId(){ /** * Save explicitly, if conditions are right */ -export function saveQueryId(query_id){ +export function setQueryId(query_id){ sessionStorage.setItem('query_id', query_id); } @@ -124,7 +102,7 @@ export function clearCache() { export function cacheQueryResults(results){ let qid = genQueryId(); - saveQueryId(qid); + setQueryId(qid); if(results.length > 0){ let search_results = {}; @@ -142,6 +120,10 @@ export function cacheQueryResults(results){ return [qid, []]; } +export function setUserId(user_id){ + sessionStorage.setItem('user_id', 'U-' + user_id); +} + export function getUserId(){ if(sessionStorage.hasOwnProperty('user_id')){ return sessionStorage.getItem('user_id'); @@ -167,10 +149,13 @@ export function getPageId(){ window.addEventListener("DOMContentLoaded", function (e) { try{ initialize(); - TimeMe.currentPageName = this.window.location.href; + TimeMe.initialize({ + currentPageName: window.location.href, + idleTimeoutInSeconds: 5 + }); TimeMe.startTimer(window.location.pathname); - } catch(e){ - + } catch(error){ + console.warn(error); } }); @@ -179,8 +164,8 @@ window.addEventListener("beforeunload", function (e) { TimeMe.stopTimer(window.location.pathname); logDwellTime('page_exit', window.location.pathname, TimeMe.getTimeOnPageInSeconds(window.location.pathname)); - } catch(e){ - + } catch(error){ + console.warn(error); } }); @@ -252,10 +237,12 @@ export class UbiEventAttributes { if(attributes != null){ Object.assign(this, attributes); } - if(object != null && object != {}){ + if(object != null && Object.keys(object).length > 0){ this.object = object; } - this.position = position; + if(position != null && Object.keys(position).length > 0){ + this.position = position; + } } } @@ -284,7 +271,8 @@ export class UbiEvent { * @returns */ static replacer(key, value){ - if(value == null) { + if(value == null || + (value.constructor == Object && Object.keys(value).length === 0)) { return undefined; } return value; From 7061eb322caa8f00b590cc15e31118a5780891e1 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Thu, 16 May 2024 10:10:47 -0700 Subject: [PATCH 07/15] adding default values --- assets/js/ubi.js | 64 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/assets/js/ubi.js b/assets/js/ubi.js index 4ea41b873f..903d60c95c 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -29,11 +29,10 @@ export function hash(str, seed=42) { /** - * In place of true authentication, this makes a hash out of the user's IP address and browser - * for tracking individual user behavior - * user_id = hash( user ip ) + '::' + hash( userAgent ) + * In place of true authentication, this makes a hash out of the user's cookie, + * which at the moment is _ga... * - * NOTE: if this function is called, but user_id starts with 'USER-', + * NOTE: if this function is called, but user_id starts with 'U-', * the function below did not complete successfully, * and userError() was called instead * @returns @@ -42,6 +41,8 @@ export async function initialize(){ let i = 1; try { + + if(!sessionStorage.hasOwnProperty('session_id')) { sessionStorage.setItem('session_id', 'S-' + guiid()); } @@ -146,6 +147,27 @@ export function getPageId(){ return location.pathname; } +function getTrail(){ + let trail = sessionStorage.getItem('trail'); + if(trail == null) + return ''; + return trail; +} + +function setTrail(){ + let trail = getTrail(); + if(trail && trail.length > 0){ + if(!trail.startsWith(window.location.pathname)){ + trail += ` › ${window.location.pathname}`; + } + } else { + trail = window.location.pathname; + } + sessionStorage.setItem('trail', trail); + + return trail; +} + window.addEventListener("DOMContentLoaded", function (e) { try{ initialize(); @@ -153,6 +175,7 @@ window.addEventListener("DOMContentLoaded", function (e) { currentPageName: window.location.href, idleTimeoutInSeconds: 5 }); + setTrail(); TimeMe.startTimer(window.location.pathname); } catch(error){ console.warn(error); @@ -163,7 +186,7 @@ window.addEventListener("beforeunload", function (e) { try{ TimeMe.stopTimer(window.location.pathname); logDwellTime('page_exit', window.location.pathname, - TimeMe.getTimeOnPageInSeconds(window.location.pathname)); + TimeMe.getTimeOnPageInSeconds(window.location.pathname)); } catch(error){ console.warn(error); } @@ -220,7 +243,11 @@ export class UbiPosition{ this.ordinal = ordinal; this.x = x; this.y = y; - this.trail = trail; + if(trail) + this.trail = trail; + else { + console.log(document.referrer); + } } } @@ -243,6 +270,31 @@ export class UbiEventAttributes { if(position != null && Object.keys(position).length > 0){ this.position = position; } + this.setDefaultValues(); + } + + setDefaultValues(){ + try{ + if(!this.hasOwnProperty('dwell_seconds') && typeof TimeMe !== 'undefined'){ + this.dwell_seconds = TimeMe.getTimeOnPageInSeconds(window.location.pathname); + } + + if(!this.hasOwnProperty('browser')){ + this.browser = window.navigator.userAgent; + } + + if(!this.hasOwnProperty('position') || this.position == null){ + const trail = getTrail(); + if(trail.length > 0){ + this.position = new UbiPosition({trail:trail}); + } + } + + // TODO: set IP + } + catch(error){ + + } } } From 1a7f00ef09b863793ddb6719b43c737de031bf02 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Wed, 22 May 2024 06:19:41 -0700 Subject: [PATCH 08/15] user_id=>client_id --- assets/js/ubi.js | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/assets/js/ubi.js b/assets/js/ubi.js index 903d60c95c..e45b1fea43 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -32,7 +32,7 @@ export function hash(str, seed=42) { * In place of true authentication, this makes a hash out of the user's cookie, * which at the moment is _ga... * - * NOTE: if this function is called, but user_id starts with 'U-', + * NOTE: if this function is called, but client_id starts with 'U-', * the function below did not complete successfully, * and userError() was called instead * @returns @@ -41,27 +41,25 @@ export async function initialize(){ let i = 1; try { - if(!sessionStorage.hasOwnProperty('session_id')) { sessionStorage.setItem('session_id', 'S-' + guiid()); } - if(sessionStorage.hasOwnProperty('user_id')){ + if(sessionStorage.hasOwnProperty('client_id')){ console.log('Already initialized UBI'); return; } // currently, the only cookie is gtag's client_id et al. if(document.cookie && document.cookie.length > 0){ - setUserId(hash(document.cookie)); + setClientId(hash(document.cookie)); return; } else { - //back up user_id method + //back up client_id method userError(); } - } catch(error){ console.log(error) } @@ -74,12 +72,11 @@ export async function initialize(){ * @returns */ function userError(){ - let user_id = guiid(); - setUserId(user_id); - return user_id; + let client_id = guiid(); + setClientId(client_id); + return client_id; } - export function genQueryId(){ const qid = 'Q-' + guiid(); sessionStorage.setItem('query_id', qid); @@ -121,15 +118,14 @@ export function cacheQueryResults(results){ return [qid, []]; } -export function setUserId(user_id){ - sessionStorage.setItem('user_id', 'U-' + user_id); +export function setClientId(client_id){ + sessionStorage.setItem('client_id', 'U-' + client_id); } -export function getUserId(){ - if(sessionStorage.hasOwnProperty('user_id')){ - return sessionStorage.getItem('user_id'); +export function getClientId(){ + if(sessionStorage.hasOwnProperty('client_id')){ + return sessionStorage.getItem('client_id'); } - return userError(); } @@ -196,7 +192,7 @@ export async function logDwellTime(action_name, page, seconds){ console.log(`${page} => ${seconds}`); let e = new UbiEvent(action_name, { message:`On page ${page} for ${seconds} seconds`, - event_attributes:{dwell_seconds:seconds}, + event_attributes:{dwell_time:seconds}, data_object:TimeMe }); logEvent(e); @@ -275,8 +271,8 @@ export class UbiEventAttributes { setDefaultValues(){ try{ - if(!this.hasOwnProperty('dwell_seconds') && typeof TimeMe !== 'undefined'){ - this.dwell_seconds = TimeMe.getTimeOnPageInSeconds(window.location.pathname); + if(!this.hasOwnProperty('dwell_time') && typeof TimeMe !== 'undefined'){ + this.dwell_time = TimeMe.getTimeOnPageInSeconds(window.location.pathname); } if(!this.hasOwnProperty('browser')){ @@ -289,7 +285,6 @@ export class UbiEventAttributes { this.position = new UbiPosition({trail:trail}); } } - // TODO: set IP } catch(error){ @@ -303,7 +298,7 @@ export class UbiEventAttributes { export class UbiEvent { constructor(action_name, {message=null, event_attributes={}, data_object={}}={}) { this.action_name = action_name; - this.user_id = getUserId(); + this.client_id = getClientId(); this.query_id = getQueryId(); this.session_id = getSessionId(); this.page_id = getPageId(); From de451a989fb245cb48815abf311bd294b5fb0a77 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Wed, 22 May 2024 06:20:30 -0700 Subject: [PATCH 09/15] Update assets/js/ubi.js Co-authored-by: Miki Signed-off-by: RasonJ <145287540+RasonJ@users.noreply.github.com> --- assets/js/ubi.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/assets/js/ubi.js b/assets/js/ubi.js index e45b1fea43..431a8a0189 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -144,22 +144,21 @@ export function getPageId(){ } function getTrail(){ - let trail = sessionStorage.getItem('trail'); - if(trail == null) - return ''; - return trail; + try { + const trail = JSON.parse(sessionStorage.getItem('trail')); + if (Array.isArray(trail)) return trail; + } catch (ex) { /* Do nothing */ } + + return []; } function setTrail(){ - let trail = getTrail(); - if(trail && trail.length > 0){ - if(!trail.startsWith(window.location.pathname)){ - trail += ` › ${window.location.pathname}`; - } - } else { - trail = window.location.pathname; - } - sessionStorage.setItem('trail', trail); + const trail = getTrail(); + // No need to add the current pathname if it is already the last element in trail + if (trail.length && trail[trail.length - 1] === window.location.pathname) + return trail; + + sessionStorage.setItem('trail', trail.concat(window.location.pathname)); return trail; } From e301b77b270d9b6b48d95b8b1488f2060a55a666 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Wed, 22 May 2024 06:26:33 -0700 Subject: [PATCH 10/15] Addressing pr feedback --- assets/js/search.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/search.js b/assets/js/search.js index 99c13fcf3d..1109dd5169 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -288,8 +288,8 @@ import * as UBI from "./ubi.js"; /** * Find item and position clicked * Modifies the ubi event data if the item is found - * @param {*} ubiEvent - * @param {*} link + * @param {*} ubiEvent - UBI.UbiEventData object + * @param {*} link - link clicked * @returns */ const setUbiClickData = (ubiEvent, link) => { From bbb55b187a9f5ac8c0369bf3db49f4c06f45dd93 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Thu, 23 May 2024 05:30:41 -0700 Subject: [PATCH 11/15] search.js cleanup --- assets/js/search.js | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/assets/js/search.js b/assets/js/search.js index 1109dd5169..3dba71ee4f 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -222,10 +222,7 @@ import * as UBI from "./ubi.js"; const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result'; if (!node || !_showingResults || node.classList.contains(CLASSNAME_HIGHLIGHTED)) return; - const link = node.querySelector('a'); - if(link){ - //xxyy logUbiEvent('item_hover', link); - } + // ToDo: UBI item_hover can go here...hover, but no click implies irrelevance in results elResults.querySelectorAll(`.${searchResultClassName}.highlighted`).forEach(el => { el.classList.remove(CLASSNAME_HIGHLIGHTED); @@ -288,7 +285,7 @@ import * as UBI from "./ubi.js"; /** * Find item and position clicked * Modifies the ubi event data if the item is found - * @param {*} ubiEvent - UBI.UbiEventData object + * @param {UbiEventData} ubiEvent - UBI.UbiEventData object * @param {*} link - link clicked * @returns */ @@ -326,11 +323,19 @@ import * as UBI from "./ubi.js"; }; - const makeUbiEvent = (name, event_type, data) => { + /** + * Helper function to populate the event structure for common + * event data elements + * @param {*} name - name of the event to build + * @param {*} event_type - type of event to tease out event parameters + * @param {*} data - an object associated with the event + * @returns + */ + const makeUbiEvent = (name, event_type, data=null) => { let e = new UBI.UbiEvent(name); let t = TimeMe.getTimeOnPageInSeconds(window.location.pathname); if(t != null){ - e.event_attributes['dwell_seconds'] = t; + e.event_attributes['dwell_time'] = t; } if(name == 'search'){ @@ -339,22 +344,18 @@ import * as UBI from "./ubi.js"; e.event_attributes.object = data; } else if(name == 'item_click') { e = setUbiClickData(e, data); - } else { - switch(event_type) { - case "event1": - break; - case "event2": - break; - default:{ - if(e.event_attributes.object == null) - e.event_attributes.object = data; - } - } + } else if(e.event_attributes.object == null){ + e.event_attributes.object = data; } return e; } + /** + * A method to retrofit and funnel gtag logging to ubi + * @param {*} name + * @param {*} data + */ const logUbiEvent = (name, data) => { let event = makeUbiEvent(name, 'default', data) UBI.logEvent(event); From 9fef7af4fdd2d8c109128b6daabced4af7604c97 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Thu, 23 May 2024 08:50:18 -0700 Subject: [PATCH 12/15] testing integration with live server --- assets/js/search.js | 5 ----- assets/js/ubi.js | 34 +++++++++++++++++++--------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/assets/js/search.js b/assets/js/search.js index 3dba71ee4f..61cb024bde 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -333,10 +333,6 @@ import * as UBI from "./ubi.js"; */ const makeUbiEvent = (name, event_type, data=null) => { let e = new UBI.UbiEvent(name); - let t = TimeMe.getTimeOnPageInSeconds(window.location.pathname); - if(t != null){ - e.event_attributes['dwell_time'] = t; - } if(name == 'search'){ e.message_type = 'QUERY'; @@ -361,7 +357,6 @@ import * as UBI from "./ubi.js"; UBI.logEvent(event); }; - const recordEvent = (name, data) => { try { gtag?.('event', name, data); diff --git a/assets/js/ubi.js b/assets/js/ubi.js index 431a8a0189..f7d5808edb 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -1,11 +1,11 @@ -function guiid() { +function genGuid() { let id = '123456-insecure'; try { id = crypto.randomUUID(); } catch(error){ - console.warn('tried to generate a guiid in an insecure context'); + console.warn('tried to generate a guid in an insecure context'); id ='10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); } @@ -43,7 +43,7 @@ export async function initialize(){ try { if(!sessionStorage.hasOwnProperty('session_id')) { - sessionStorage.setItem('session_id', 'S-' + guiid()); + sessionStorage.setItem('session_id', 'S-' + genGuid()); } if(sessionStorage.hasOwnProperty('client_id')){ @@ -72,13 +72,13 @@ export async function initialize(){ * @returns */ function userError(){ - let client_id = guiid(); + let client_id = genGuid(); setClientId(client_id); return client_id; } export function genQueryId(){ - const qid = 'Q-' + guiid(); + const qid = 'Q-' + genGuid(); sessionStorage.setItem('query_id', qid); return qid; } @@ -134,7 +134,7 @@ export function getSessionId(){ return sessionStorage.getItem('session_id'); } - let session_id = guiid(); + let session_id = genGuid(); sessionStorage.setItem('session_id', session_id); return session_id; } @@ -200,7 +200,8 @@ export async function logDwellTime(action_name, page, seconds){ export async function logEvent(event){ try { //=>146.190.147.150 - fetch('http://localhost:9200/ubi/docs', { + //w.i.p. dev fetch('http://localhost:9200/ubi_events/_doc', { + fetch('http://146.190.147.150:9200/ubi_events/_doc', { method: 'POST', headers: { 'Accept': 'application/json, text/plain, */*', @@ -210,7 +211,7 @@ export async function logEvent(event){ }).then(res => res.json()) .then(res => console.log(res)) .catch((error) => { - console.log(error) + console.warn(error) }); } catch (e) { console.warn('Ubi error: ' + JSON.stringify(e)); @@ -228,9 +229,6 @@ export class UbiEventData { this.object_id = id; this.description = description; this.object_detail = details; - - //override if using key_field's and values - this.key_value = id; } } export class UbiPosition{ @@ -249,11 +247,17 @@ export class UbiPosition{ export class UbiEventAttributes { /** - * Attributes, other than `object` or `position` should be in the form of + * Tries to prepopulate common event attributes + * The developer can add an `object` that the user interacted with and + * the site `position` information relevant to the event + * + * Attributes, other than `object` or `position` can be added in the form: * attributes['item1'] = 1 * attributes['item2'] = '2' * - * The object member is reserved for further, relevant object payloads or classes + * @param {*} attributes: object with general event attributes + * @param {*} object: the data object the user interacted with + * @param {*} position: the site position information */ constructor({attributes={}, object=null, position=null}={}) { if(attributes != null){ @@ -284,10 +288,10 @@ export class UbiEventAttributes { this.position = new UbiPosition({trail:trail}); } } - // TODO: set IP + // ToDo: set IP } catch(error){ - + console.log(error); } } } From ce0da0653e717f2a1d4e6fa3a756a4d3640e4799 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Mon, 27 May 2024 10:07:42 -0700 Subject: [PATCH 13/15] Fixing `trail` bugs --- assets/js/ubi.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/assets/js/ubi.js b/assets/js/ubi.js index f7d5808edb..69917a4764 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -145,20 +145,26 @@ export function getPageId(){ function getTrail(){ try { - const trail = JSON.parse(sessionStorage.getItem('trail')); - if (Array.isArray(trail)) return trail; + var trail = sessionStorage.getItem('trail'); + if(trail && trail.length > 0){ + trail = trail.split(','); + + if (Array.isArray(trail)) + return trail; + } } catch (ex) { /* Do nothing */ } return []; } function setTrail(){ - const trail = getTrail(); + var trail = getTrail(); // No need to add the current pathname if it is already the last element in trail if (trail.length && trail[trail.length - 1] === window.location.pathname) return trail; - sessionStorage.setItem('trail', trail.concat(window.location.pathname)); + trail = trail.concat(window.location.pathname); + sessionStorage.setItem('trail', trail); return trail; } @@ -200,8 +206,9 @@ export async function logDwellTime(action_name, page, seconds){ export async function logEvent(event){ try { //=>146.190.147.150 - //w.i.p. dev fetch('http://localhost:9200/ubi_events/_doc', { - fetch('http://146.190.147.150:9200/ubi_events/_doc', { + //w.i.p. dev + fetch('http://localhost:9200/ubi_events/_doc', { + //fetch('http://146.190.147.150:9200/ubi_events/_doc', { method: 'POST', headers: { 'Accept': 'application/json, text/plain, */*', @@ -239,7 +246,9 @@ export class UbiPosition{ if(trail) this.trail = trail; else { - console.log(document.referrer); + const trail = getTrail(); + if(trail && trail.length > 0) + this.trail = trail; } } } @@ -287,6 +296,7 @@ export class UbiEventAttributes { if(trail.length > 0){ this.position = new UbiPosition({trail:trail}); } + } } // ToDo: set IP } From 488c74da9d452afd7bdefe304db903f42bc142eb Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Tue, 28 May 2024 06:32:41 -0700 Subject: [PATCH 14/15] Adding {% ubi.html %} to track page loads and 404 redirects --- 404.md | 1 + _includes/ubi.html | 19 +++++++++++++++++++ assets/js/ubi.js | 21 +++++++++++++++++---- 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 _includes/ubi.html diff --git a/404.md b/404.md index 60a1bc8847..a12aba7705 100644 --- a/404.md +++ b/404.md @@ -5,6 +5,7 @@ layout: default heading_anchors: false nav_exclude: true --- +{% include ubi.html %} ## Oops, this isn't the page you're looking for. diff --git a/_includes/ubi.html b/_includes/ubi.html new file mode 100644 index 0000000000..431c1711bc --- /dev/null +++ b/_includes/ubi.html @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/assets/js/ubi.js b/assets/js/ubi.js index 69917a4764..df0f7c852d 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -194,7 +194,6 @@ window.addEventListener("beforeunload", function (e) { }); export async function logDwellTime(action_name, page, seconds){ - console.log(`${page} => ${seconds}`); let e = new UbiEvent(action_name, { message:`On page ${page} for ${seconds} seconds`, event_attributes:{dwell_time:seconds}, @@ -203,6 +202,21 @@ export async function logDwellTime(action_name, page, seconds){ logEvent(e); } + +export async function logUbiMessage(event_type, message_type, message){ + let e = new UbiEvent(event_type, { + message_type:message_type, + message:message + }); + logEvent(e); +} +//expose globally with Ubi moniker @see: ubi.html +window.logUbiMessage = logUbiMessage; + +/** + * + * @param {UbiEvent} event + */ export async function logEvent(event){ try { //=>146.190.147.150 @@ -297,7 +311,6 @@ export class UbiEventAttributes { this.position = new UbiPosition({trail:trail}); } } - } // ToDo: set IP } catch(error){ @@ -309,7 +322,7 @@ export class UbiEventAttributes { export class UbiEvent { - constructor(action_name, {message=null, event_attributes={}, data_object={}}={}) { + constructor(action_name, {message_type='INFO', message=null, event_attributes={}, data_object={}}={}) { this.action_name = action_name; this.client_id = getClientId(); this.query_id = getQueryId(); @@ -317,7 +330,7 @@ export class UbiEvent { this.page_id = getPageId(); this.timestamp = Date.now(); - this.message_type = 'INFO'; + this.message_type = message_type; if( message ) this.message = message; From ab3847b6cfca77f942c58dacb1821dbcfc0d2c22 Mon Sep 17 00:00:00 2001 From: RasonJ <145287540+RasonJ@users.noreply.github.com> Date: Thu, 30 May 2024 11:05:34 -0700 Subject: [PATCH 15/15] update per schema changes --- assets/js/ubi.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/assets/js/ubi.js b/assets/js/ubi.js index df0f7c852d..7108ac886a 100644 --- a/assets/js/ubi.js +++ b/assets/js/ubi.js @@ -245,8 +245,8 @@ export async function logEvent(event){ *********************************************************************************************/ export class UbiEventData { - constructor(type, id=null, description=null, details=null) { - this.object_type = type; + constructor(object_type, id=null, description=null, details=null) { + this.object_id_field = object_type; this.object_id = id; this.description = description; this.object_detail = details; @@ -305,6 +305,18 @@ export class UbiEventAttributes { this.browser = window.navigator.userAgent; } + if(!this.hasOwnProperty('page_id')){ + this.page_id = window.location.pathname; + } + if(!this.hasOwnProperty('session_id')){ + this.session_id = getSessionId(); + } + + if(!this.hasOwnProperty('page_id')){ + this.page_id = getPageId(); + } + + if(!this.hasOwnProperty('position') || this.position == null){ const trail = getTrail(); if(trail.length > 0){ @@ -326,8 +338,6 @@ export class UbiEvent { this.action_name = action_name; this.client_id = getClientId(); this.query_id = getQueryId(); - this.session_id = getSessionId(); - this.page_id = getPageId(); this.timestamp = Date.now(); this.message_type = message_type;