This is an automated email from the ASF dual-hosted git repository. poorejc pushed a commit to branch FLAGON-434 in repository https://gitbox.apache.org/repos/asf/incubator-flagon-useralejs.git
commit 397a22b46b52b0c732fae8bf1e8652fab863530d Author: poorejc <[email protected]> AuthorDate: Fri Aug 9 14:48:39 2019 -0400 [FLAGON-440] created sendOnRefresh function to call via attachEventListeners so data isn't lost for events that trigger refreshes --- build/UserAleWebExtension/content.js | 238 ++++++++++++++++++++--------------- build/userale-2.0.2.js | 238 ++++++++++++++++++++--------------- build/userale-2.0.2.min.js | 2 +- example/index_form.html | 81 ++++++++++++ src/attachHandlers.js | 20 ++- src/sendLogs.js | 18 +++ 6 files changed, 385 insertions(+), 212 deletions(-) diff --git a/build/UserAleWebExtension/content.js b/build/UserAleWebExtension/content.js index ab02b35..9efa490 100644 --- a/build/UserAleWebExtension/content.js +++ b/build/UserAleWebExtension/content.js @@ -468,11 +468,133 @@ function selectorizePath(path) { * limitations under the License. */ +var sendIntervalId = null; + +/** + * Initializes the log queue processors. + * @param {Array} logs Array of logs to append to. + * @param {Object} config Configuration object to use when logging. + */ +function initSender(logs, config) { + if (sendIntervalId !== null) { + clearInterval(sendIntervalId); + } + + sendIntervalId = sendOnInterval(logs, config); + sendOnClose(logs, config); +} + +/** + * Checks the provided log array on an interval, flushing the logs + * if the queue has reached the threshold specified by the provided config. + * @param {Array} logs Array of logs to read from. + * @param {Object} config Configuration object to be read from. + * @return {Number} The newly created interval id. + */ +function sendOnInterval(logs, config) { + return setInterval(function() { + if (!config.on) { + return; + } + + if (logs.length >= config.logCountThreshold) { + sendLogs(logs.slice(0), config.url, 0); // Send a copy + logs.splice(0); // Clear array reference (no reassignment) + } + }, config.transmitInterval); +} + +/** + * Provides a simplified send function that can be called before events that would + * refresh page can resolve so that log queue ('logs) can be shipped immediately. This + * is different than sendOnClose because browser security practices prevent you from + * listening the process responsible for window navigation actions, in action (e.g., refresh; + * you can only detect, after the fact, the process responsible for the current window state. + * @param {Array} logs Array of logs to read from. + * @param {Object} config Configuration object to be read from. + */ +function sendOnRefresh(logs, config) { + if (!config.on) { + return; + } + if (logs.length > 0) { + sendLogs(logs, config.url, 1); + } +} + +/** + * Attempts to flush the remaining logs when the window is closed. + * @param {Array} logs Array of logs to be flushed. + * @param {Object} config Configuration object to be read from. + */ +function sendOnClose(logs, config) { + if (!config.on) { + return; + } + + if (navigator.sendBeacon) { + window.addEventListener('unload', function() { + navigator.sendBeacon(config.url, JSON.stringify(logs)); + }); + } else { + window.addEventListener('beforeunload', function() { + if (logs.length > 0) { + sendLogs(logs, config.url, 1); + } + }); + } +} + +/** + * Sends the provided array of logs to the specified url, + * retrying the request up to the specified number of retries. + * @param {Array} logs Array of logs to send. + * @param {string} url URL to send the POST request to. + * @param {Number} retries Maximum number of attempts to send the logs. + */ +function sendLogs(logs, url, retries) { + var req = new XMLHttpRequest(); + + var data = JSON.stringify(logs); + + req.open('POST', url); + req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status !== 200) { + if (retries > 0) { + sendLogs(logs, url, retries--); + } + } + }; + + req.send(data); +} + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var events; var bufferBools; var bufferedEvents; //@todo: Investigate drag events and their behavior var intervalEvents = ['click', 'focus', 'blur', 'input', 'change', 'mouseover', 'submit']; +var refreshEvents; var windowEvents = ['load', 'blur', 'focus']; /** @@ -512,8 +634,7 @@ function defineDetails(config) { 'drag' : null, 'drop' : null, 'keydown' : config.logDetails ? function(e) { return { 'key' : e.keyCode, 'ctrl' : e.ctrlKey, 'alt' : e.altKey, 'shift' : e.shiftKey, 'meta' : e.metaKey }; } : null, - 'mouseover' : null, - 'submit' : null + 'mouseover' : null }; bufferBools = {}; @@ -522,6 +643,10 @@ function defineDetails(config) { 'scroll' : function() { return { 'x' : window.scrollX, 'y' : window.scrollY }; }, 'resize' : function() { return { 'width' : window.outerWidth, 'height' : window.outerHeight }; } }; + + refreshEvents = { + 'submit' : null + }; } /** @@ -556,6 +681,13 @@ function attachHandlers(config) { }, true); }); + Object.keys(refreshEvents).forEach(function(ev) { + document.addEventListener(ev, function(e) { + packageLog(e, events[ev]); + sendOnRefresh(logs,config); + }, true); + }); + windowEvents.forEach(function(ev) { window.addEventListener(ev, function(e) { packageLog(e, function() { return { 'window' : true }; }); @@ -582,108 +714,6 @@ function attachHandlers(config) { * limitations under the License. */ -var sendIntervalId = null; - -/** - * Initializes the log queue processors. - * @param {Array} logs Array of logs to append to. - * @param {Object} config Configuration object to use when logging. - */ -function initSender(logs, config) { - if (sendIntervalId !== null) { - clearInterval(sendIntervalId); - } - - sendIntervalId = sendOnInterval(logs, config); - sendOnClose(logs, config); -} - -/** - * Checks the provided log array on an interval, flushing the logs - * if the queue has reached the threshold specified by the provided config. - * @param {Array} logs Array of logs to read from. - * @param {Object} config Configuration object to be read from. - * @return {Number} The newly created interval id. - */ -function sendOnInterval(logs, config) { - return setInterval(function() { - if (!config.on) { - return; - } - - if (logs.length >= config.logCountThreshold) { - sendLogs(logs.slice(0), config.url, 0); // Send a copy - logs.splice(0); // Clear array reference (no reassignment) - } - }, config.transmitInterval); -} - -/** - * Attempts to flush the remaining logs when the window is closed. - * @param {Array} logs Array of logs to be flushed. - * @param {Object} config Configuration object to be read from. - */ -function sendOnClose(logs, config) { - if (!config.on) { - return; - } - - if (navigator.sendBeacon) { - window.addEventListener('unload', function() { - navigator.sendBeacon(config.url, JSON.stringify(logs)); - }); - } else { - window.addEventListener('beforeunload', function() { - if (logs.length > 0) { - sendLogs(logs, config.url, 1); - } - }); - } -} - -/** - * Sends the provided array of logs to the specified url, - * retrying the request up to the specified number of retries. - * @param {Array} logs Array of logs to send. - * @param {string} url URL to send the POST request to. - * @param {Number} retries Maximum number of attempts to send the logs. - */ -function sendLogs(logs, url, retries) { - var req = new XMLHttpRequest(); - - var data = JSON.stringify(logs); - - req.open('POST', url); - req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status !== 200) { - if (retries > 0) { - sendLogs(logs, url, retries--); - } - } - }; - - req.send(data); -} - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - var config$1 = {}; var logs$1 = []; var started = false; diff --git a/build/userale-2.0.2.js b/build/userale-2.0.2.js index 28bf257..f0ba930 100644 --- a/build/userale-2.0.2.js +++ b/build/userale-2.0.2.js @@ -447,11 +447,133 @@ var userale = (function (exports) { * limitations under the License. */ + var sendIntervalId = null; + + /** + * Initializes the log queue processors. + * @param {Array} logs Array of logs to append to. + * @param {Object} config Configuration object to use when logging. + */ + function initSender(logs, config) { + if (sendIntervalId !== null) { + clearInterval(sendIntervalId); + } + + sendIntervalId = sendOnInterval(logs, config); + sendOnClose(logs, config); + } + + /** + * Checks the provided log array on an interval, flushing the logs + * if the queue has reached the threshold specified by the provided config. + * @param {Array} logs Array of logs to read from. + * @param {Object} config Configuration object to be read from. + * @return {Number} The newly created interval id. + */ + function sendOnInterval(logs, config) { + return setInterval(function() { + if (!config.on) { + return; + } + + if (logs.length >= config.logCountThreshold) { + sendLogs(logs.slice(0), config.url, 0); // Send a copy + logs.splice(0); // Clear array reference (no reassignment) + } + }, config.transmitInterval); + } + + /** + * Provides a simplified send function that can be called before events that would + * refresh page can resolve so that log queue ('logs) can be shipped immediately. This + * is different than sendOnClose because browser security practices prevent you from + * listening the process responsible for window navigation actions, in action (e.g., refresh; + * you can only detect, after the fact, the process responsible for the current window state. + * @param {Array} logs Array of logs to read from. + * @param {Object} config Configuration object to be read from. + */ + function sendOnRefresh(logs, config) { + if (!config.on) { + return; + } + if (logs.length > 0) { + sendLogs(logs, config.url, 1); + } + } + + /** + * Attempts to flush the remaining logs when the window is closed. + * @param {Array} logs Array of logs to be flushed. + * @param {Object} config Configuration object to be read from. + */ + function sendOnClose(logs, config) { + if (!config.on) { + return; + } + + if (navigator.sendBeacon) { + window.addEventListener('unload', function() { + navigator.sendBeacon(config.url, JSON.stringify(logs)); + }); + } else { + window.addEventListener('beforeunload', function() { + if (logs.length > 0) { + sendLogs(logs, config.url, 1); + } + }); + } + } + + /** + * Sends the provided array of logs to the specified url, + * retrying the request up to the specified number of retries. + * @param {Array} logs Array of logs to send. + * @param {string} url URL to send the POST request to. + * @param {Number} retries Maximum number of attempts to send the logs. + */ + function sendLogs(logs, url, retries) { + var req = new XMLHttpRequest(); + + var data = JSON.stringify(logs); + + req.open('POST', url); + req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status !== 200) { + if (retries > 0) { + sendLogs(logs, url, retries--); + } + } + }; + + req.send(data); + } + + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var events; var bufferBools; var bufferedEvents; //@todo: Investigate drag events and their behavior var intervalEvents = ['click', 'focus', 'blur', 'input', 'change', 'mouseover', 'submit']; + var refreshEvents; var windowEvents = ['load', 'blur', 'focus']; /** @@ -491,8 +613,7 @@ var userale = (function (exports) { 'drag' : null, 'drop' : null, 'keydown' : config.logDetails ? function(e) { return { 'key' : e.keyCode, 'ctrl' : e.ctrlKey, 'alt' : e.altKey, 'shift' : e.shiftKey, 'meta' : e.metaKey }; } : null, - 'mouseover' : null, - 'submit' : null + 'mouseover' : null }; bufferBools = {}; @@ -501,6 +622,10 @@ var userale = (function (exports) { 'scroll' : function() { return { 'x' : window.scrollX, 'y' : window.scrollY }; }, 'resize' : function() { return { 'width' : window.outerWidth, 'height' : window.outerHeight }; } }; + + refreshEvents = { + 'submit' : null + }; } /** @@ -535,6 +660,13 @@ var userale = (function (exports) { }, true); }); + Object.keys(refreshEvents).forEach(function(ev) { + document.addEventListener(ev, function(e) { + packageLog(e, events[ev]); + sendOnRefresh(logs,config); + }, true); + }); + windowEvents.forEach(function(ev) { window.addEventListener(ev, function(e) { packageLog(e, function() { return { 'window' : true }; }); @@ -561,108 +693,6 @@ var userale = (function (exports) { * limitations under the License. */ - var sendIntervalId = null; - - /** - * Initializes the log queue processors. - * @param {Array} logs Array of logs to append to. - * @param {Object} config Configuration object to use when logging. - */ - function initSender(logs, config) { - if (sendIntervalId !== null) { - clearInterval(sendIntervalId); - } - - sendIntervalId = sendOnInterval(logs, config); - sendOnClose(logs, config); - } - - /** - * Checks the provided log array on an interval, flushing the logs - * if the queue has reached the threshold specified by the provided config. - * @param {Array} logs Array of logs to read from. - * @param {Object} config Configuration object to be read from. - * @return {Number} The newly created interval id. - */ - function sendOnInterval(logs, config) { - return setInterval(function() { - if (!config.on) { - return; - } - - if (logs.length >= config.logCountThreshold) { - sendLogs(logs.slice(0), config.url, 0); // Send a copy - logs.splice(0); // Clear array reference (no reassignment) - } - }, config.transmitInterval); - } - - /** - * Attempts to flush the remaining logs when the window is closed. - * @param {Array} logs Array of logs to be flushed. - * @param {Object} config Configuration object to be read from. - */ - function sendOnClose(logs, config) { - if (!config.on) { - return; - } - - if (navigator.sendBeacon) { - window.addEventListener('unload', function() { - navigator.sendBeacon(config.url, JSON.stringify(logs)); - }); - } else { - window.addEventListener('beforeunload', function() { - if (logs.length > 0) { - sendLogs(logs, config.url, 1); - } - }); - } - } - - /** - * Sends the provided array of logs to the specified url, - * retrying the request up to the specified number of retries. - * @param {Array} logs Array of logs to send. - * @param {string} url URL to send the POST request to. - * @param {Number} retries Maximum number of attempts to send the logs. - */ - function sendLogs(logs, url, retries) { - var req = new XMLHttpRequest(); - - var data = JSON.stringify(logs); - - req.open('POST', url); - req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status !== 200) { - if (retries > 0) { - sendLogs(logs, url, retries--); - } - } - }; - - req.send(data); - } - - /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - var config$1 = {}; var logs$1 = []; exports.started = false; diff --git a/build/userale-2.0.2.min.js b/build/userale-2.0.2.min.js index 85b781b..0a3b0c8 100644 --- a/build/userale-2.0.2.min.js +++ b/build/userale-2.0.2.min.js @@ -15,4 +15,4 @@ * limitations under the License. * @preserved */ -var userale=function(n){"use strict";var a,i,u,l,s,c,d,f,t="2.0.2";function e(n,o){Object.keys(o).forEach(function(t){if("userFromParams"===t){var e=function(t){var e=new RegExp("[?&]"+t+"(=([^&#]*)|&|#|$)"),n=window.location.href.match(e);return n&&n[2]?decodeURIComponent(n[2].replace(/\+/g," ")):null}(o[t]);e&&(n.userId=e)}n[t]=o[t]})}var o,r,m,p=null,g=null;function v(t,e){if(!i.on)return!1;var n=null;e&&(n=e(t));var o=function(t){return{milli:Math.floor(t),micro:Number((t%1).toFixed( [...] \ No newline at end of file +var userale=function(n){"use strict";var a,i,u,l,s,c,d,f,t="2.0.2";function e(n,o){Object.keys(o).forEach(function(t){if("userFromParams"===t){var e=function(t){var e=new RegExp("[?&]"+t+"(=([^&#]*)|&|#|$)"),n=window.location.href.match(e);return n&&n[2]?decodeURIComponent(n[2].replace(/\+/g," ")):null}(o[t]);e&&(n.userId=e)}n[t]=o[t]})}var m=null,p=null;function o(t,e){if(!i.on)return!1;var n=null;e&&(n=e(t));var o=function(t){return{milli:Math.floor(t),micro:Number((t%1).toFixed(3))}}( [...] \ No newline at end of file diff --git a/example/index_form.html b/example/index_form.html new file mode 100644 index 0000000..7095e3d --- /dev/null +++ b/example/index_form.html @@ -0,0 +1,81 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<html> +<head> + <title>UserAleJS - Example Page</title> + <script + src="file:///Users/jpoore/Documents/Apache_Flagon/test/incubator-flagon-useralejs/build/userale-2.0.2.min.js" + data-url="http://localhost:8000/" + data-user="example-user" + data-log-details="true" + data-version="2.0.2" + data-tool="Apache UserALE.js Example" + ></script> + <script type="text/javascript"> + window.userale.filter(function (log) { + var type_array = ['mouseup', 'mousedown', 'dblclick', 'blur'] + var logType_array = ['interval'] + return !type_array.includes(log.type) && !logType_array.includes(log.logType); + }); + </script> +</head> +<body> + <br> + <br> + <br> + <div class="container"> + <button id="test_button">Click me!</button> + </div> +<!--Play around with this mapping function to transform your logs! + <script type="text/javascript"> + window.userale.map(function (log) { + var targetsForLabels = ["button#test_button"]; + if (targetsForLabels.includes(log.target)) { + return Object.assign({}, log, { CustomLabel: "Click me!" }); + } else { + return log; + } + }); + </script> +--> + <br> + <br> + <br> + <form id="test_text_input"> + <label>Test field: <input type="text"></label> + <br><br> + <button type="submit">Submit form</button> + </form> + <br> + <br> + <br> + <select name="cars"> + <option value="volvo">Volvo</option> + <option value="saab">Saab</option> + <option value="fiat">Fiat</option> + <option value="audi">Audi</option> + </select> + <br> + <br> + <br> + <form id="test_radio_input"> + <input type="radio" name="gender" value="male" checked> Male<br> + <input type="radio" name="gender" value="female"> Female<br> + <input type="radio" name="gender" value="other"> Other + </form> +</body> +</html> diff --git a/src/attachHandlers.js b/src/attachHandlers.js index f025a4e..43b8aba 100644 --- a/src/attachHandlers.js +++ b/src/attachHandlers.js @@ -15,14 +15,18 @@ * limitations under the License. */ +import { logs } from './packageLogs'; import { packageLog } from './packageLogs.js'; -import { packageIntervalLog } from './packageLogs'; +import { packageIntervalLog} from './packageLogs'; +import { sendOnRefresh } from "./sendLogs"; + var events; var bufferBools; var bufferedEvents; //@todo: Investigate drag events and their behavior var intervalEvents = ['click', 'focus', 'blur', 'input', 'change', 'mouseover', 'submit']; +var refreshEvents; var windowEvents = ['load', 'blur', 'focus']; /** @@ -62,8 +66,7 @@ export function defineDetails(config) { 'drag' : null, 'drop' : null, 'keydown' : config.logDetails ? function(e) { return { 'key' : e.keyCode, 'ctrl' : e.ctrlKey, 'alt' : e.altKey, 'shift' : e.shiftKey, 'meta' : e.metaKey }; } : null, - 'mouseover' : null, - 'submit' : null + 'mouseover' : null }; bufferBools = {}; @@ -72,6 +75,10 @@ export function defineDetails(config) { 'scroll' : function() { return { 'x' : window.scrollX, 'y' : window.scrollY }; }, 'resize' : function() { return { 'width' : window.outerWidth, 'height' : window.outerHeight }; } }; + + refreshEvents = { + 'submit' : null + }; } /** @@ -106,6 +113,13 @@ export function attachHandlers(config) { }, true); }); + Object.keys(refreshEvents).forEach(function(ev) { + document.addEventListener(ev, function(e) { + packageLog(e, events[ev]); + sendOnRefresh(logs,config); + }, true); + }); + windowEvents.forEach(function(ev) { window.addEventListener(ev, function(e) { packageLog(e, function() { return { 'window' : true }; }); diff --git a/src/sendLogs.js b/src/sendLogs.js index 8f1bc86..b057a3e 100644 --- a/src/sendLogs.js +++ b/src/sendLogs.js @@ -52,6 +52,24 @@ export function sendOnInterval(logs, config) { } /** + * Provides a simplified send function that can be called before events that would + * refresh page can resolve so that log queue ('logs) can be shipped immediately. This + * is different than sendOnClose because browser security practices prevent you from + * listening the process responsible for window navigation actions, in action (e.g., refresh; + * you can only detect, after the fact, the process responsible for the current window state. + * @param {Array} logs Array of logs to read from. + * @param {Object} config Configuration object to be read from. + */ +export function sendOnRefresh(logs, config) { + if (!config.on) { + return; + } + if (logs.length > 0) { + sendLogs(logs, config.url, 1); + } +} + +/** * Attempts to flush the remaining logs when the window is closed. * @param {Array} logs Array of logs to be flushed. * @param {Object} config Configuration object to be read from.
