diff --git a/.gitignore b/.gitignore
index 14413157..ec7937d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.idea
node_modules
.DS_store
+test/cafe/node_modules
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
diff --git a/client/app/config/common.js b/client/app/config/common.js
old mode 100644
new mode 100755
diff --git a/client/app/config/jiff.js b/client/app/config/jiff.js
old mode 100644
new mode 100755
diff --git a/client/app/controllers/analystController.js b/client/app/controllers/analystController.js
old mode 100644
new mode 100755
diff --git a/client/app/controllers/clientController.js b/client/app/controllers/clientController.js
old mode 100644
new mode 100755
index b60dd1e1..bf5b0fdb
--- a/client/app/controllers/clientController.js
+++ b/client/app/controllers/clientController.js
@@ -1,6 +1,6 @@
/* global alertify, $ */
-define(['jquery', 'controllers/tableController', 'controllers/jiffController', 'alertify', 'alertify_defaults', 'table_template'],
- function ($, tableController, jiffController, alertify, _, table_template) {
+define(['jquery', 'controllers/tableController', 'controllers/jiffController', 'controllers/usabilityController', 'alertify', 'alertify_defaults', 'table_template'],
+ function ($, tableController, jiffController, usabilityController, alertify, _, table_template) {
var client = (function () {
/**
* Displays the given submission as the last submission in
@@ -94,7 +94,7 @@ define(['jquery', 'controllers/tableController', 'controllers/jiffController', '
dataType: 'text'
}).then(function (response) {
JSON.parse(response); // verify response is json (error responses are string messages)
-
+
var $parent = $('#session, #participation-code').parent();
$parent.removeClass('has-error').addClass('has-success has-feedback');
$parent.find('.success-icon').removeClass('hidden').addClass('show');
@@ -104,6 +104,7 @@ define(['jquery', 'controllers/tableController', 'controllers/jiffController', '
callback && callback(true);
}).catch(function (err) {
var errorMsg = SERVER_ERR;
+ usabilityController.addValidationError("SESSION_INFO_ERROR");
if (err && err.hasOwnProperty('responseText') && err.responseText !== undefined) {
errorMsg = err.responseText;
}
@@ -127,11 +128,13 @@ define(['jquery', 'controllers/tableController', 'controllers/jiffController', '
var $session = $('#session');
if (!validateSessionInput($session, false)) {
errors.push(SESSION_KEY_ERROR);
+ usabilityController.addValidationError("SESSION_KEY_ERROR");
}
var $participationCode = $('#participation-code');
if (!validateSessionInput($participationCode, false)) {
errors.push(PARTICIPATION_CODE_ERROR);
+ usabilityController.addValidationError("PARTICIPATION_CODE_ERROR");
}
// Validate the remaining components after session and
@@ -139,12 +142,14 @@ define(['jquery', 'controllers/tableController', 'controllers/jiffController', '
var validateRemainingComponents = function (result) {
if (!result) {
errors.push(SESSION_PARTICIPATION_CODE_SERVER_ERROR);
+ usabilityController.addValidationError("SESSION_PARTICIPATION_CODE_SERVER_ERROR");
}
// Verify confirmation check box was checked
var verifyChecked = $('#verify').is(':checked');
if (!verifyChecked) {
errors.push(UNCHECKED_ERR);
+ usabilityController.addValidationError("UNCHECKED_ERR");
}
// Verify additional questions
@@ -194,6 +199,7 @@ define(['jquery', 'controllers/tableController', 'controllers/jiffController', '
}
if (errors.indexOf(errorMsg) === -1) {
errors.push(errorMsg);
+ usabilityController.addValidationError("CELL_ERROR");
}
};
tableController.registerErrorHandler(errorHandler);
@@ -264,7 +270,7 @@ define(['jquery', 'controllers/tableController', 'controllers/jiffController', '
var text = $(questions_text[q]).text();
text = text.replace(/\s+/g, ' '); // Replace many white spaces with just one space.
data_submission['questions'][text] = question_data;
- questions_public[text] = Object.assign({}, question_data);
+ questions_public[text] = Object.assign({}, question_data);
}
// Handle table data, tables are represented as 2D associative arrays
@@ -274,6 +280,12 @@ define(['jquery', 'controllers/tableController', 'controllers/jiffController', '
data_submission[tables_data[i].name] = tables_data[i].data;
}
+ if (document.getElementById('choose-file').files.length > 0) {
+ usabilityController.dataPrefilled();
+ }
+
+ data_submission['usability'] = usabilityController.analytics;
+
// Secret share / mask the data.
jiffController.client.submit(session, participationCode, data_submission, function (err, response) {
if (err == null || err === 200) {
@@ -286,8 +298,10 @@ define(['jquery', 'controllers/tableController', 'controllers/jiffController', '
} else if (err === 0 || err === 500) {
// check for status 0 or status 500 (Server not reachable.)
error(SERVER_ERR);
+ usabilityController.addValidationError("SERVER_ERR");
} else {
error(GENERIC_SUBMISSION_ERR);
+ usabilityController.addValidationError("GENERIC_SUBMISSION_ERR");
}
la.stop();
diff --git a/client/app/controllers/jiffController.js b/client/app/controllers/jiffController.js
old mode 100644
new mode 100755
index 4e44401a..fee7d342
--- a/client/app/controllers/jiffController.js
+++ b/client/app/controllers/jiffController.js
@@ -99,6 +99,16 @@ define(['mpc', 'pki', 'BigNumber', 'jiff', 'jiff_bignumber', 'jiff_restAPI', 'ta
values.push(dataSubmission['questions'][q.question][q.option]);
}
+ for (var k = 0; k < ordering.usability.length; k++) {
+ const m = ordering.usability[k].metric;
+ const f = ordering.usability[k].field;
+ if (f !== null) {
+ values.push(dataSubmission.usability[m][f]);
+ } else {
+ values.push(dataSubmission.usability[m]);
+ }
+ }
+
// Handle jiff errors returned from server
var options = {
onError: function (errorString) {
@@ -149,18 +159,22 @@ define(['mpc', 'pki', 'BigNumber', 'jiff', 'jiff_bignumber', 'jiff_restAPI', 'ta
// Meta-info
var ordering = mpc.consistentOrdering(table_template);
- var submitters = JSON.parse(msg);
+ msg = JSON.parse(msg);
+ var submitters = msg['submitters'];
+ var resubmission_avg = msg['resubmission_avg'];
// Compute and Format
var promise = mpc.compute(jiff, submitters, ordering);
promise = mpc.format(promise, submitters, ordering);
promise.then(function (result) {
jiff.disconnect(false, false);
+ result['usability']['resubmission_avg'] = resubmission_avg;
callback(result);
}).catch(function (err) {
error(err);
});
});
+
};
// Exports
@@ -169,7 +183,7 @@ define(['mpc', 'pki', 'BigNumber', 'jiff', 'jiff_bignumber', 'jiff_restAPI', 'ta
submit: clientSubmit
},
analyst: {
- computeAndFormat: computeAndFormat
+ computeAndFormat
}
}
});
diff --git a/client/app/controllers/tableController.js b/client/app/controllers/tableController.js
old mode 100644
new mode 100755
index 98dcec4b..5aca7a2d
--- a/client/app/controllers/tableController.js
+++ b/client/app/controllers/tableController.js
@@ -1013,6 +1013,11 @@ define(['jquery', 'Handsontable', 'table_template', 'filesaver', 'alertify', 'qt
return colWidths;
}
+ function saveUsability(usability, session) {
+ var json = JSON.stringify(usability);
+ filesaver.saveAs(new Blob([json], {type: 'application/json'}), 'Usability_' + session + '.json');
+ }
+
function saveQuestions(questions, session) {
if (questions == null) {
return;
@@ -1050,6 +1055,7 @@ define(['jquery', 'Handsontable', 'table_template', 'filesaver', 'alertify', 'qt
fillData,
saveTables,
saveQuestions,
+ saveUsability,
displayReadTable,
resetTableWidth,
updateTableWidth,
diff --git a/client/app/controllers/usabilityController.js b/client/app/controllers/usabilityController.js
new file mode 100644
index 00000000..a8c26bc3
--- /dev/null
+++ b/client/app/controllers/usabilityController.js
@@ -0,0 +1,109 @@
+define(['table_template'], function (table_template) {
+
+ let analytics = {};
+ let timers = {};
+
+ function initialize() {
+ for (var metric of table_template.usability) {
+ if (typeof(metric) === 'string') {
+ analytics[metric] = 0;
+ } else if (typeof(metric) === 'object') {
+ var key = Object.keys(metric)[0];
+ var fields = metric[key];
+ analytics[key] = {};
+
+ for (var f of fields) {
+ analytics[key][f] = 0;
+ }
+ }
+ }
+
+ for (let k of Object.keys(analytics.time_spent)) {
+
+ if (k === 'page') {
+ timers[k] = new Date();
+ } else {
+ timers[k] = null;
+ }
+
+ $('#session-area').on("mouseenter", function() {
+ startTimer(k);
+ });
+
+ $('#session-area').on("mouseleave", function() {
+ endTimer(k);
+ });
+ }
+ window.addEventListener('blur', endTimer);
+ window.addEventListener('beforeunload', endTimer);
+ }
+
+ function detectBrave() {
+ // initial assertions
+ if (!window.google_onload_fired &&
+ navigator.userAgent &&
+ !navigator.userAgent.includes('Chrome'))
+ return false;
+
+ // set up test
+ var test = document.createElement('iframe');
+ test.style.display = 'none';
+ document.body.appendChild(test);
+
+ // empty frames only have this attribute set in Brave Shield
+ var is_brave = (test.contentWindow.google_onload_fired === true);
+
+ // teardown test
+ test.parentNode.removeChild(test);
+
+ return is_brave;
+ }
+
+ function startTimer(key) {
+ timers[key] = new Date();
+ };
+
+ function endTimer(key) {
+ if (typeof(key) !== 'string') {
+ key = 'page';
+ }
+ const endDate = new Date();
+ const spentTime = endDate.getTime() - timers[key].getTime();
+ analytics.time_spent[key] += spentTime;
+ };
+
+ function saveBrowser() {
+ // check Edge
+ var ua=navigator.userAgent,tem,M=ua.match(/(opera|chrome|safari|edge|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
+ var browser = M[0].toLowerCase();
+
+ if (detectBrave) {
+ analytics.browser.brave += 1;
+ return;
+ }
+
+ for (let key in analytics.browser) {
+ if (browser.includes(key)) {
+ analytics.browser[key] += 1;
+ return;
+ }
+ }
+ analytics.browser.other += 1;
+ }
+
+ function addValidationError(err) {
+ analytics.validation_errors[err] += 1;
+ }
+
+ function dataPrefilled() {
+ analytics.data_prefilled += 1;
+ }
+
+ return {
+ addValidationError,
+ analytics,
+ dataPrefilled,
+ initialize,
+ saveBrowser
+ };
+});
diff --git a/client/app/data/bwwc.js b/client/app/data/bwwc.js
old mode 100644
new mode 100755
diff --git a/client/app/data/pacesetters.js b/client/app/data/pacesetters.js
old mode 100644
new mode 100755
index bc34c6df..eafc6435
--- a/client/app/data/pacesetters.js
+++ b/client/app/data/pacesetters.js
@@ -203,6 +203,26 @@ define([], function () {
}
]
}
+ ],
+ 'usability': [
+ 'data_prefilled',
+ {'time_spent': ['page', 'session_area', 'tables-area', 'review-and-submit', 'amount-spent', 'addressable-spend', 'number-MBEs']},
+ {'browser': ['brave', 'chrome', 'edge', 'msie', 'firefox', 'opera', 'other', 'safari']
+ },
+ {'validation_errors': [
+ 'SESSION_KEY_ERROR',
+ 'SESSION_INFO_ERROR',
+ 'PARTICIPATION_CODE_ERROR',
+ 'SESSION_PARTICIPATION_CODE_SERVER_ERROR',
+ 'UNCHECKED_ERR',
+ 'GENERIC_TABLE_ERR',
+ 'SERVER_ERR',
+ 'GENERIC_SUBMISSION_ERR',
+ 'NAN_EMPTY_CELLS',
+ 'SEMANTIC_CELLS',
+ 'CELL_ERROR'
+ ]
+ }
]
}
});
diff --git a/client/app/helper/alertify_defaults.js b/client/app/helper/alertify_defaults.js
old mode 100644
new mode 100755
diff --git a/client/app/helper/array.reduce-polyfill.min.js b/client/app/helper/array.reduce-polyfill.min.js
old mode 100644
new mode 100755
diff --git a/client/app/helper/drop_sheet.js b/client/app/helper/drop_sheet.js
old mode 100644
new mode 100755
diff --git a/client/app/helper/mpc.js b/client/app/helper/mpc.js
index 8a9e2f78..ac9804d4 100644
--- a/client/app/helper/mpc.js
+++ b/client/app/helper/mpc.js
@@ -19,6 +19,7 @@ define([], function () {
var consistentOrdering = function (table_template) {
var tables = [];
var questions = [];
+ var usability = [];
// order tables
for (var i = 0; i < table_template.tables.length; i++) {
var table_def = table_template.tables[i];
@@ -47,7 +48,20 @@ define([], function () {
}
}
}
- return { tables: tables, questions: questions };
+
+ for (let metric of table_template.usability) {
+ if (typeof(metric) === 'string') {
+ usability.push({metric: metric, field: null});
+ } else if (typeof(metric) === 'object') {
+ const key = Object.keys(metric)[0];
+ const fields = metric[key];
+
+ for (let f of fields) {
+ usability.push({metric: key, field: f});
+ }
+ }
+ }
+ return { tables, questions, usability };
};
var compute = function (jiff_instance, submitters, ordering) {
@@ -55,7 +69,7 @@ define([], function () {
// sum all received shares for that entry, and reveal the sum
// for that element to the analyst in order.
var shares = [];
- for (var k = 0; k < ordering.tables.length + ordering.questions.length; k++) {
+ for (var k = 0; k < ordering.tables.length + ordering.questions.length + ordering.usability.length; k++) {
var subid = submitters[0];
var entry_sum = jiff_instance.share(null, null, [1, 's1'], [subid]);
@@ -98,7 +112,6 @@ define([], function () {
shares.push(promise);
}
-
return Promise.all(shares);
};
@@ -115,9 +128,12 @@ define([], function () {
// }
var format = function (resultsPromise, submitters, ordering) {
return resultsPromise.then(function (results) {
+
var averages = {}; // results array will be transformed to an object of the correct form
var questions = {};
var deviations = {};
+ var usability = {};
+
for (var i = 0; i < ordering.tables.length; i++) {
var table = ordering.tables[i].table;
var row = ordering.tables[i].row;
@@ -156,13 +172,29 @@ define([], function () {
questions[question][label] = results[i+j].toString();
}
- return { averages: averages, questions: questions, deviations: deviations };
+ for (let k = 0; k < ordering.usability.length; k++) {
+
+ const m = ordering.usability[k].metric;
+ const f = ordering.usability[k].field;
+ const value = results[i+j+k].c[0].toString();
+
+ if (f === null) {
+ usability[m] = value;
+ } else {
+ if (!(m in usability)) {
+ usability[m] = {};
+ }
+ usability[m][f] = value;
+
+ }
+ }
+ return { averages, questions, deviations, usability };
});
};
return {
- consistentOrdering: consistentOrdering,
- compute: compute,
- format: format
+ consistentOrdering,
+ compute,
+ format
}
});
diff --git a/client/app/helper/pki.js b/client/app/helper/pki.js
old mode 100644
new mode 100755
diff --git a/client/app/helper/sheetjsw.js b/client/app/helper/sheetjsw.js
old mode 100644
new mode 100755
diff --git a/client/app/index.js b/client/app/index.js
old mode 100644
new mode 100755
diff --git a/client/app/session.js b/client/app/session.js
old mode 100644
new mode 100755
diff --git a/client/app/track.js b/client/app/track.js
old mode 100644
new mode 100755
diff --git a/client/app/unmask.js b/client/app/unmask.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/ResizeSensor.js b/client/app/vendor/ResizeSensor.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/alertify.js b/client/app/vendor/alertify.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/bootstrap.min.js b/client/app/vendor/bootstrap.min.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/filesaver.js b/client/app/vendor/filesaver.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/forge.js b/client/app/vendor/forge.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/jquery.js b/client/app/vendor/jquery.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/jquery_qtip.js b/client/app/vendor/jquery_qtip.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/ladda.js b/client/app/vendor/ladda.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/require.js b/client/app/vendor/require.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/spin.js b/client/app/vendor/spin.js
old mode 100644
new mode 100755
diff --git a/client/app/vendor/xlsx.js b/client/app/vendor/xlsx.js
old mode 100644
new mode 100755
diff --git a/client/app/views/clientView.js b/client/app/views/clientView.js
old mode 100644
new mode 100755
index a302ff4a..69ae71c7
--- a/client/app/views/clientView.js
+++ b/client/app/views/clientView.js
@@ -1,5 +1,5 @@
-define(['jquery', 'controllers/clientController', 'controllers/tableController', 'helper/drop_sheet', 'spin', 'Ladda', 'ResizeSensor', 'alertify', 'table_template', 'bootstrap'],
- function ($, clientController, tableController, DropSheet, Spinner, Ladda, ResizeSensor, alertify, table_template) {
+define(['jquery', 'controllers/clientController', 'controllers/tableController', 'controllers/usabilityController', 'helper/drop_sheet', 'spin', 'Ladda', 'ResizeSensor', 'alertify', 'table_template', 'bootstrap'],
+ function ($, clientController, tableController, usabilityController, DropSheet, Spinner, Ladda, ResizeSensor, alertify, table_template) {
function createQuestionText(text) {
var p = document.createElement('p');
@@ -66,12 +66,16 @@ define(['jquery', 'controllers/clientController', 'controllers/tableController',
// Hide by default
$('#additional-questions').hide();
+ usabilityController.initialize();
+
tableController.createTableElems(table_template.tables, '#tables-area');
displaySurveyQuestions();
// Create the tabless
var tables = tableController.makeTables(table_template.tables);
+ usabilityController.saveBrowser();
+ // TODO
//createResizeSensors(tables); THIS FUNCTION BREAKS THINGS I THINK! -IRA
var totals_table = null;
diff --git a/client/app/views/sessionView.js b/client/app/views/sessionView.js
old mode 100644
new mode 100755
diff --git a/client/app/views/trackView.js b/client/app/views/trackView.js
old mode 100644
new mode 100755
diff --git a/client/app/views/unmaskView.js b/client/app/views/unmaskView.js
old mode 100644
new mode 100755
index 95d31a08..1e565085
--- a/client/app/views/unmaskView.js
+++ b/client/app/views/unmaskView.js
@@ -2,6 +2,10 @@
define(['jquery', 'controllers/jiffController', 'controllers/tableController', 'helper/drop_sheet', 'alertify', 'table_template'],
function ($, jiffController, tableController, DropSheet, alertify, table_template) {
+
+ document.getElementById('session').value = 'gv5bsr26k3vghtkpr6rs6nd3n4';
+ document.getElementById('session-password').value = '2rnategc605cmxqwt2wvmcpkec';
+
function error(msg) {
alertify.alert('Error!', msg);
}
@@ -26,13 +30,15 @@ define(['jquery', 'controllers/jiffController', 'controllers/tableController', '
var privateKey = e.target.result;
jiffController.analyst.computeAndFormat(sessionKey, sessionPass, privateKey, error, function (result) {
- var questions = result.questions;
+ var questions = result['questions'];
+ var usability = result['usability'];
+ delete result['usability'];
+ delete result['questions'];
// for tables only
var averages = result.averages;
var deviations = result.deviations;
-
// download averages and deviations
tableController.saveTables(averages, sessionKey, 'Averages');
tableController.saveTables(deviations, sessionKey, 'Standard_Deviations');
@@ -40,6 +46,10 @@ define(['jquery', 'controllers/jiffController', 'controllers/tableController', '
tableController.saveQuestions(questions, sessionKey);
}
+ if (usability != null) {
+ tableController.saveUsability(usability, sessionKey);
+ }
+
// Only display averages in the table
tableController.createTableElems(table_template.tables, '#tables-area');
tableController.displayReadTable(averages);
diff --git a/client/closed.html b/client/closed.html
old mode 100644
new mode 100755
diff --git a/client/create.html b/client/create.html
old mode 100644
new mode 100755
diff --git a/client/definitions.html b/client/definitions.html
old mode 100644
new mode 100755
diff --git a/client/images/accept.png b/client/images/accept.png
old mode 100644
new mode 100755
diff --git a/client/images/bu.gif b/client/images/bu.gif
old mode 100644
new mode 100755
diff --git a/client/images/bu.png b/client/images/bu.png
old mode 100644
new mode 100755
diff --git a/client/images/bwwc.png b/client/images/bwwc.png
old mode 100644
new mode 100755
diff --git a/client/images/cancel.png b/client/images/cancel.png
old mode 100644
new mode 100755
diff --git a/client/images/expand.png b/client/images/expand.png
old mode 100644
new mode 100755
diff --git a/client/images/gbcc-small.png b/client/images/gbcc-small.png
old mode 100644
new mode 100755
diff --git a/client/images/gbcc.jpg b/client/images/gbcc.jpg
old mode 100644
new mode 100755
diff --git a/client/images/gbcc.png b/client/images/gbcc.png
old mode 100644
new mode 100755
diff --git a/client/index.html b/client/index.html
index a4abd85d..799eb4b4 100755
--- a/client/index.html
+++ b/client/index.html
@@ -70,8 +70,8 @@