created basic analytics dashboard
This commit is contained in:
parent
637ff4dc70
commit
26b2fafc3d
|
@ -19,13 +19,6 @@ var mongoose = require('mongoose'),
|
||||||
*/
|
*/
|
||||||
exports.uploadPDF = function(req, res, next) {
|
exports.uploadPDF = function(req, res, next) {
|
||||||
|
|
||||||
//console.log('inside uploadPDF');
|
|
||||||
|
|
||||||
// console.log('\n\nProperty Descriptor\n-----------');
|
|
||||||
// console.log(Object.getOwnPropertyDescriptor(req.files.file, 'path'));
|
|
||||||
|
|
||||||
//console.log(req.file);
|
|
||||||
|
|
||||||
if(req.file){
|
if(req.file){
|
||||||
var pdfFile = req.file;
|
var pdfFile = req.file;
|
||||||
var _user = req.user;
|
var _user = req.user;
|
||||||
|
@ -376,7 +369,7 @@ exports.formByID = function(req, res, next, id) {
|
||||||
message: 'Form is invalid'
|
message: 'Form is invalid'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Form.findById(id).populate('admin').exec(function(err, form) {
|
Form.findById(id).populate('admin').populate('submissions').exec(function(err, form) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
} else if (form === undefined || form === null) {
|
} else if (form === undefined || form === null) {
|
||||||
|
|
|
@ -48,17 +48,37 @@ var ButtonSchema = new Schema({
|
||||||
|
|
||||||
var VisitorDataSchema = new Schema({
|
var VisitorDataSchema = new Schema({
|
||||||
referrer: {
|
referrer: {
|
||||||
type: String
|
type: String,
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
lastActiveField: {
|
lastActiveField: {
|
||||||
type: Schema.Types.ObjectId
|
type: Schema.Types.ObjectId
|
||||||
},
|
},
|
||||||
timeElapsed: {
|
timeElapsed: {
|
||||||
type: Number
|
type: Number,
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
isSubmitted: {
|
isSubmitted: {
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
ipAddr: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
deviceType: {
|
||||||
|
type: String,
|
||||||
|
enum: ['desktop', 'phone', 'tablet', 'other'],
|
||||||
|
default: 'other',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
userAgent: {
|
||||||
|
type: String
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,7 +35,8 @@
|
||||||
"tableExport.jquery.plugin": "^1.5.1",
|
"tableExport.jquery.plugin": "^1.5.1",
|
||||||
"js-yaml": "^3.6.1",
|
"js-yaml": "^3.6.1",
|
||||||
"angular-ui-select": "whitef0x0/ui-select#compiled",
|
"angular-ui-select": "whitef0x0/ui-select#compiled",
|
||||||
"angular-translate": "~2.11.0"
|
"angular-translate": "~2.11.0",
|
||||||
|
"ng-device-detector": "^3.0.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"angular-bootstrap": "^0.14.0",
|
"angular-bootstrap": "^0.14.0",
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
var ApplicationConfiguration = (function() {
|
var ApplicationConfiguration = (function() {
|
||||||
// Init module configuration options
|
// Init module configuration options
|
||||||
var applicationModuleName = 'NodeForm';
|
var applicationModuleName = 'NodeForm';
|
||||||
var applicationModuleVendorDependencies = ['duScroll', 'ui.select', 'cgBusy', 'ngSanitize', 'vButton', 'ngResource', 'NodeForm.templates', 'ui.router', 'ui.bootstrap', 'ui.utils', 'pascalprecht.translate'];
|
var applicationModuleVendorDependencies = ['duScroll', 'ui.select', 'cgBusy', 'ngSanitize', 'vButton', 'ngResource', 'NodeForm.templates', 'ui.router', 'ui.bootstrap', 'ui.utils', 'pascalprecht.translate', 'ng.deviceDetector'];
|
||||||
|
|
||||||
// Add a new vertical module
|
// Add a new vertical module
|
||||||
var registerModule = function(moduleName, dependencies) {
|
var registerModule = function(moduleName, dependencies) {
|
||||||
|
|
|
@ -61,9 +61,17 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||||
CLICK_FIELDS_FOOTER: 'Click on fields to add them here',
|
CLICK_FIELDS_FOOTER: 'Click on fields to add them here',
|
||||||
|
|
||||||
//Edit Submissions View
|
//Edit Submissions View
|
||||||
TOTAL_VIEWS: 'Total Views',
|
TOTAL_VIEWS: 'total unique visits',
|
||||||
SUBMISSIONS: 'Submissions',
|
RESPONSES: 'responses',
|
||||||
CONVERSION_RATE: 'Conversion Rate',
|
COMPLETION_RATE: 'completion rate',
|
||||||
|
AVERAGE_TIME_TO_COMPLETE: 'avg. completion time',
|
||||||
|
|
||||||
|
DESKTOP_AND_LAPTOP: 'Desktops/Laptops',
|
||||||
|
TABLETS: 'Tablets',
|
||||||
|
PHONES: 'Phones',
|
||||||
|
OTHER: 'Other',
|
||||||
|
UNIQUE_VISITS: 'Unique Visits',
|
||||||
|
|
||||||
FIELD_TITLE: 'Field Title',
|
FIELD_TITLE: 'Field Title',
|
||||||
FIELD_VIEWS: 'Field Views',
|
FIELD_VIEWS: 'Field Views',
|
||||||
FIELD_DROPOFF: 'User dropoff rate at this field',
|
FIELD_DROPOFF: 'User dropoff rate at this field',
|
||||||
|
|
28
public/modules/forms/admin/css/edit-submissions-view.css
Normal file
28
public/modules/forms/admin/css/edit-submissions-view.css
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
.analytics .header-title {
|
||||||
|
font-size: 1em;
|
||||||
|
color: #bab8b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics .header-numbers {
|
||||||
|
font-size: 4em;
|
||||||
|
padding-bottom: 0.1em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
border-bottom: #fafafa solid 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics .detailed-title {
|
||||||
|
font-size: 1.8em;
|
||||||
|
margin-bottom: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics .detailed-row {
|
||||||
|
padding-bottom: 0.8em;
|
||||||
|
}
|
||||||
|
.analytics .detailed-row .row {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
.analytics .detailed-row .row.header {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #bab8b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
|
@ -15,6 +15,87 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||||
rows: []
|
rows: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(function initController(){
|
||||||
|
|
||||||
|
var defaultFormFields = _.cloneDeep($scope.myform.form_fields);
|
||||||
|
|
||||||
|
//Iterate through form's submissions
|
||||||
|
|
||||||
|
var submissions = _.cloneDeep($scope.myform.submissions);
|
||||||
|
for(var i = 0; i < submissions.length; i++){
|
||||||
|
for(var x = 0; x < submissions[i].form_fields; x++){
|
||||||
|
var oldValue = submissions[i].form_fields[x].fieldValue || '';
|
||||||
|
submissions[i].form_fields[x] = _.merge(defaultFormFields, submissions[i].form_fields);
|
||||||
|
submissions[i].form_fields[x].fieldValue = oldValue;
|
||||||
|
}
|
||||||
|
submissions[i].selected = false;
|
||||||
|
}
|
||||||
|
// console.log('after textField2: '+data[0].form_fields[1].fieldValue);
|
||||||
|
|
||||||
|
$scope.table.rows = submissions;
|
||||||
|
|
||||||
|
// console.log('form submissions successfully fetched');
|
||||||
|
// console.log( JSON.parse(JSON.stringify($scope.submissions)) ) ;
|
||||||
|
// console.log( JSON.parse(JSON.stringify($scope.myform.form_fields)) );
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Analytics Functions
|
||||||
|
*/
|
||||||
|
$scope.AverageTimeElapsed = (function(){
|
||||||
|
var totalTime = 0;
|
||||||
|
var numSubmissions = $scope.table.rows.length;
|
||||||
|
|
||||||
|
for(var i=0; i<$scope.table.rows.length; i++){
|
||||||
|
totalTime += $scope.table.rows[i].timeElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(totalTime/numSubmissions);
|
||||||
|
return totalTime/numSubmissions;
|
||||||
|
})();
|
||||||
|
|
||||||
|
$scope.DeviceStatistics = (function(){
|
||||||
|
var stats = {
|
||||||
|
desktop: null,
|
||||||
|
tablet: null,
|
||||||
|
phone: null,
|
||||||
|
other: null
|
||||||
|
};
|
||||||
|
var newStatItem = function(){
|
||||||
|
return {
|
||||||
|
visits: 0,
|
||||||
|
responses: 0,
|
||||||
|
completion: 0,
|
||||||
|
average_time: 0,
|
||||||
|
total_time: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var visitors = $scope.myform.analytics.visitors;
|
||||||
|
|
||||||
|
console.log(visitors);
|
||||||
|
for(var i=0; i<visitors.length; i++){
|
||||||
|
var visitor = visitors[i];
|
||||||
|
var deviceType = visitor.deviceType;
|
||||||
|
|
||||||
|
if(!stats[deviceType]){
|
||||||
|
stats[deviceType] = newStatItem();
|
||||||
|
}
|
||||||
|
stats[deviceType].visits++;
|
||||||
|
|
||||||
|
stats[deviceType].total_time =+ visitor.timeElapsed;
|
||||||
|
stats[deviceType].average_time = stats[deviceType].total_time/stats[deviceType].visits;
|
||||||
|
|
||||||
|
if(stats[deviceType].isSubmitted) stats[deviceType].responses++;
|
||||||
|
|
||||||
|
stats[deviceType].completion = stats[deviceType].response/stats[deviceType].visits;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(stats);
|
||||||
|
return stats;
|
||||||
|
})();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Table Functions
|
** Table Functions
|
||||||
*/
|
*/
|
||||||
|
@ -41,36 +122,6 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//Fetch and display submissions of Form
|
//Fetch and display submissions of Form
|
||||||
$scope.initFormSubmissions = function(){
|
|
||||||
$http.get('/forms/'+$scope.myform._id+'/submissions')
|
|
||||||
.success(function(data, status, headers){
|
|
||||||
|
|
||||||
var _tmpSubFormFields,
|
|
||||||
defaultFormFields = _.cloneDeep($scope.myform.form_fields);
|
|
||||||
|
|
||||||
// console.log('before textField2: '+data[0].form_fields[1].fieldValue);
|
|
||||||
|
|
||||||
//Iterate through form's submissions
|
|
||||||
for(var i=0; i<data.length; i++){
|
|
||||||
for(var x=0; x<data[i].form_fields; x++){
|
|
||||||
var oldValue = data[i].form_fields[x].fieldValue || '';
|
|
||||||
data[i].form_fields[x] = _.merge(defaultFormFields, data[i].form_fields);
|
|
||||||
data[i].form_fields[x].fieldValue = oldValue;
|
|
||||||
}
|
|
||||||
data[i].selected = false;
|
|
||||||
}
|
|
||||||
// console.log('after textField2: '+data[0].form_fields[1].fieldValue);
|
|
||||||
|
|
||||||
$scope.table.rows = data;
|
|
||||||
|
|
||||||
// console.log('form submissions successfully fetched');
|
|
||||||
// console.log( JSON.parse(JSON.stringify($scope.submissions)) ) ;
|
|
||||||
// console.log( JSON.parse(JSON.stringify($scope.myform.form_fields)) );
|
|
||||||
})
|
|
||||||
.error(function(err){
|
|
||||||
console.error('Could not fetch form submissions.\nError: '+err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//Delete selected submissions of Form
|
//Delete selected submissions of Form
|
||||||
$scope.deleteSelectedSubmissions = function(){
|
$scope.deleteSelectedSubmissions = function(){
|
||||||
|
@ -109,7 +160,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log($scope.table.rows);
|
console.log($scope.table.rows);
|
||||||
|
|
||||||
angular.element('#table-submission-data').tableExport({type: type, escape:false});
|
angular.element('#table-submission-data').tableExport({type: type, escape:false});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,43 +1,236 @@
|
||||||
<div class="submissions-table row container" ng-init="initFormSubmissions()">
|
<div class="submissions-table row container">
|
||||||
<div class="row">
|
<div class="row text-center analytics">
|
||||||
<div class="col-xs-4">
|
<div class="row col-xs-12 header-title">
|
||||||
{{ 'TOTAL_VIEWS' | translate }}: {{myform.analytics.views}}
|
<div class="col-xs-3">
|
||||||
</div>
|
{{ 'TOTAL_VIEWS' | translate }}
|
||||||
|
|
||||||
<div class="col-xs-4">
|
|
||||||
{{ 'SUBMISSIONS' | translate }}: {{myform.analytics.submissions}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xs-4">
|
|
||||||
{{ 'CONVERSION_RATE' | translate }}: {{myform.analytics.conversionRate}}%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
|
|
||||||
<div class="col-xs-2">
|
|
||||||
<strong>{{ 'FIELD_TITLE' | translate }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-2">
|
|
||||||
<strong>{{ 'FIELD_VIEWS' | translate }}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-3">
|
||||||
<strong>{{ 'FIELD_DROPOFF' | translate }}</strong>
|
{{ 'RESPONSES' | translate }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
{{ 'COMPLETION_RATE' | translate }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12" ng-repeat="fieldStats in myform.analytics.fields">
|
|
||||||
|
|
||||||
<div class="col-xs-2">
|
<div class="row col-xs-12 header-numbers">
|
||||||
{{fieldStats.field.title}}
|
<div class="col-xs-3">
|
||||||
</div>
|
{{myform.analytics.views}}
|
||||||
<div class="col-xs-2">
|
|
||||||
{{fieldStats.totalViews}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-3">
|
||||||
{{fieldStats.dropoffRate}}%
|
{{myform.analytics.submissions}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
{{myform.analytics.conversionRate | number:0}}%
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
{{AverageTimeElapsed | secondsToDateTime | date:'mm:ss'}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row col-xs-12 detailed-title">
|
||||||
|
<div class="col-xs-3">
|
||||||
|
{{ 'DESKTOP_AND_LAPTOP' | translate }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
{{ 'TABLETS' | translate }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
{{ 'PHONES' | translate }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
{{ 'OTHER' | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row col-xs-12 detailed-row">
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'UNIQUE_VISITS' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.desktop.visits}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'UNIQUE_VISITS' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.tablet.visits}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'UNIQUE_VISITS' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.tablet.visits}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'UNIQUE_VISITS' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.other.visits}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row col-xs-12 detailed-row">
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'RESPONSES' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.desktop.responses}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'RESPONSES' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.tablet.responses}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'RESPONSES' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.phone.responses}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'RESPONSES' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.other.responses}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row col-xs-12 detailed-row">
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'COMPLETION_RATE' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.desktop.completion}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'COMPLETION_RATE' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.tablet.completion}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'COMPLETION_RATE' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.phone.completion}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'COMPLETION_RATE' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.other.completion}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row col-xs-12 detailed-row">
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.desktop.average_time | secondsToDateTime | date:'mm:ss'}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.tablet.average_time | secondsToDateTime | date:'mm:ss'}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.phone.average_time | secondsToDateTime | date:'mm:ss'}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<div class="row header">
|
||||||
|
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{DeviceStatistics.other.average_time | secondsToDateTime | date:'mm:ss'}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row col-xs-12">
|
||||||
|
|
||||||
|
<div class="col-xs-2">
|
||||||
|
<strong>{{ 'FIELD_TITLE' | translate }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-2">
|
||||||
|
<strong>{{ 'FIELD_VIEWS' | translate }}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<strong>{{ 'FIELD_DROPOFF' | translate }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row col-xs-12" ng-repeat="fieldStats in myform.analytics.fields">
|
||||||
|
|
||||||
|
<div class="col-xs-2">
|
||||||
|
{{fieldStats.field.title}}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-2">
|
||||||
|
{{fieldStats.totalViews}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-4">
|
||||||
|
{{fieldStats.dropoffRate}}%
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -116,7 +309,7 @@
|
||||||
{{row.percentageComplete}}%
|
{{row.percentageComplete}}%
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{row.timeElapsed}}
|
{{row.timeElapsed | secondsToDateTime | date:'mm:ss'}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{row.device.name}}, {{row.device.type}}
|
{{row.device.name}}, {{row.device.type}}
|
||||||
|
|
|
@ -6,7 +6,11 @@ angular.module('forms').run(['Menus',
|
||||||
// Set top bar menu items
|
// Set top bar menu items
|
||||||
Menus.addMenuItem('topbar', 'My Forms', 'forms', '', '/forms', false);
|
Menus.addMenuItem('topbar', 'My Forms', 'forms', '', '/forms', false);
|
||||||
}
|
}
|
||||||
]).filter('formValidity',
|
]).filter('secondsToDateTime', [function() {
|
||||||
|
return function(seconds) {
|
||||||
|
return new Date(1970, 0, 1).setSeconds(seconds);
|
||||||
|
};
|
||||||
|
}]).filter('formValidity',
|
||||||
function(){
|
function(){
|
||||||
return function(formObj){
|
return function(formObj){
|
||||||
if(formObj && formObj.form_fields && formObj.visible_form_fields){
|
if(formObj && formObj.form_fields && formObj.visible_form_fields){
|
||||||
|
|
|
@ -6,22 +6,42 @@
|
||||||
.module('forms')
|
.module('forms')
|
||||||
.factory('SendVisitorData', SendVisitorData);
|
.factory('SendVisitorData', SendVisitorData);
|
||||||
|
|
||||||
SendVisitorData.$inject = ['Socket', '$state'];
|
SendVisitorData.$inject = ['Socket', '$state', '$http', 'deviceDetector'];
|
||||||
|
|
||||||
function SendVisitorData(Socket, $state) {
|
function SendVisitorData(Socket, $state, $http) {
|
||||||
|
|
||||||
// Create a controller method for sending visitor data
|
// Create a controller method for sending visitor data
|
||||||
function send(form, lastActiveIndex, timeElapsed) {
|
function send(form, lastActiveIndex, timeElapsed, deviceDetector) {
|
||||||
|
|
||||||
// Create a new message object
|
// Create a new message object
|
||||||
var visitorData = {
|
var visitorData = {
|
||||||
referrer: document.referrer,
|
referrer: document.referrer,
|
||||||
isSubmitted: form.submitted,
|
isSubmitted: form.submitted,
|
||||||
formId: form._id,
|
formId: form._id,
|
||||||
lastActiveField: form.form_fields[lastActiveIndex]._id,
|
lastActiveField: form.form_fields[lastActiveIndex]._id,
|
||||||
timeElapsed: timeElapsed
|
timeElapsed: timeElapsed,
|
||||||
|
//@TODO @FIXME: David: Need to make this get the language from the HTTP Header instead
|
||||||
|
language: window.navigator.userLanguage || window.navigator.language
|
||||||
};
|
};
|
||||||
Socket.emit('form-visitor-data', visitorData);
|
|
||||||
|
$http.get('http://jsonip.com/').success(function(response) {
|
||||||
|
visitorData.ipAddr = response['ip']+'';
|
||||||
|
}).error(function(error) {
|
||||||
|
console.error('Could not get users\'s ip');
|
||||||
|
visitorData.ipAddr = '';
|
||||||
|
}).finally(function(){
|
||||||
|
visitorData.userAgent = deviceDetector.raw;
|
||||||
|
|
||||||
|
if(deviceDetector.isTablet()) {
|
||||||
|
visitorData.deviceType = 'tablet';
|
||||||
|
}else if(deviceDetector.isMobile()){
|
||||||
|
visitorData.deviceType = 'phone';
|
||||||
|
}else {
|
||||||
|
visitorData.deviceType = 'desktop';
|
||||||
|
}
|
||||||
|
Socket.emit('form-visitor-data', visitorData);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(){
|
function init(){
|
||||||
|
|
Loading…
Reference in a new issue