Skip to content

Commit

Permalink
Merge pull request #832 from e-mission/integrate_odk_compatible_surve…
Browse files Browse the repository at this point in the history
…ys_using_enketo

Integrate odk compatible surveys using enketo
  • Loading branch information
shankari committed May 27, 2022
2 parents 84e0e9e + 136343f commit e2f2143
Show file tree
Hide file tree
Showing 40 changed files with 120,660 additions and 145 deletions.
3 changes: 2 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"angular-translate": "^2.18.1",
"angular-translate-loader-static-files": "^2.18.1",
"angular-translate-interpolation-messageformat": "^2.18.1",
"bottleneck": "^2.19.5"
"bottleneck": "^2.19.5",
"jquery-xml2json": "^0.0.8"
}
}
9 changes: 9 additions & 0 deletions www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@

"control":{
"profile": "Profile",
"edit-demographics": "Edit Demographics",
"tracking": "Tracking",
"app-status": "App Status",
"medium-accuracy": "Medium accuracy",
Expand Down Expand Up @@ -155,6 +156,7 @@
"time": "Time",
"mode": "Mode",
"purpose": "Purpose",
"survey": "Details",
"unlabeled": "All Unlabeled",
"invalid-ebike": "Invalid",
"to-label": "To Label",
Expand All @@ -167,6 +169,7 @@
"choose-mode": "Mode 📝 ",
"choose-replaced-mode": "Replaces 📝",
"choose-purpose": "Purpose 📝",
"choose-survey": "Add Trip Details 📝 ",
"select-mode-scroll": "Mode (👇 for more)",
"select-replaced-mode-scroll": "Replaces (👇 for more)",
"select-purpose-scroll": "Purpose (👇 for more)",
Expand Down Expand Up @@ -396,5 +399,11 @@
"sensor_explanation":{
"button-accept": "OK",
"button-decline": "Stop"
},
"survey": {
"prev-survey-found": "Found previous survey response",
"use-prior-response": "Use prior response",
"edit-response": "Edit response",
"move-on": "Move on"
}
}
17 changes: 17 additions & 0 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
<link href="common/img/sprites/spritesmith/quests/bosses/quest.bosses.css" rel="stylesheet">
<link rel="stylesheet" href="lib/ng-walkthrough/css/ng-walkthrough.css">
<link href="lib/angularjs-slider/dist/rzslider.css" rel="stylesheet">
<!-- <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,600&amp;subset=latin,cyrillic-ext,cyrillic,greek-ext,greek,vietnamese,latin-ext' rel='stylesheet' type='text/css'> -->
<link href="manual_lib/enketo/css/formhub.css" media="all" rel="stylesheet">
<!--<link type="text/css" href="manual_lib/enketo/css/plain.css" media="all" rel="stylesheet" />-->
<!--<link type="text/css" href="manual_lib/enketo/css/grid.css" media="all" rel="stylesheet" />-->
<link href="manual_lib/enketo/css/formhub-print.css" media="print" rel="stylesheet">
<!--<link type="text/css" href="manual_lib/enketo/css/plain-print.css" media="print" rel="stylesheet" />-->
<!--<link type="text/css" href="manual_lib/enketo/css/grid-print.css" media="print" rel="stylesheet" />-->
<link href="manual_lib/fontawesome/css/all.min.css" rel="stylesheet">

<!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
Expand All @@ -29,6 +36,7 @@

<!-- ionic/angularjs js -->
<script src="lib/jquery/dist/jquery.min.js"></script>
<script src="lib/jquery-xml2json/src/xml2json.js"></script>
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="lib/ionic-toast/dist/ionic-toast.bundle.min.js"></script>
<script src="lib/angular-translate/angular-translate.min.js"></script>
Expand Down Expand Up @@ -58,6 +66,7 @@
<script src="lib/angular-cookies/angular-cookies.min.js"></script>
<script src="lib/angularLocalStorage/dist/angularLocalStorage.min.js"></script>
<script src="lib/angularjs-slider/dist/rzslider.min.js"></script>
<script src="manual_lib/enketo/enketo-bundle.js"></script>
<!-- Manually installed javascript for libraries that are not published through bower -->
<!-- cordova script (this will be a 404 in the browser) -->
<script type="text/javascript" src="cordova.js"></script>
Expand All @@ -81,6 +90,7 @@
<script src="js/main.js"></script>
<script src="js/recent.js"></script>
<script src="js/survey/survey.js"></script>
<script src="js/survey/input-matcher.js"></script>
<script src="js/survey/one-click-button.js"></script>
<script src="js/incident/post-trip-manual.js"></script>
<script src="js/survey/multilabel/infinite_scroll_filters.js"></script>
Expand All @@ -103,6 +113,13 @@
<script src="js/common/place-detail.js"></script>
<script src="js/common/services.js"></script>
<script src="js/survey/external/launch.js"></script>
<script src="js/survey/enketo/answer.js"></script>
<script src="js/survey/enketo/launch.js"></script>
<script src="js/survey/enketo/service.js"></script>
<script src="js/survey/enketo/infinite_scroll_filters.js"></script>
<script src="js/survey/enketo/enketo-trip-button.js"></script>
<script src="js/survey/enketo/enketo-demographics.js"></script>
<script src="js/survey/enketo/enketo-preview.js"></script>
<script src="js/heatmap.js"></script>
<script src="js/metrics.js"></script>
<script src="js/goals.js"></script>
Expand Down
1 change: 1 addition & 0 deletions www/js/control/general-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ angular.module('emission.main.control',['emission.services',
'emission.main.metrics.factory',
'emission.stats.clientstats',
'emission.plugin.kvstore',
'emission.survey.enketo.demographics',
'emission.plugin.logger'])

.controller('ControlCtrl', function($scope, $window, $ionicScrollDelegate,
Expand Down
151 changes: 16 additions & 135 deletions www/js/diary/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

angular.module('emission.main.diary.services', ['emission.plugin.logger',
'emission.services', 'emission.main.common.services',
'emission.incident.posttrip.manual', 'emission.survey.multilabel.services'])
'emission.incident.posttrip.manual'])
.factory('DiaryHelper', function(CommonGraph, PostTripManualMarker, $translate){
var dh = {};
// dh.expandEarlierOrLater = function(id) {
Expand Down Expand Up @@ -372,127 +372,20 @@ angular.module('emission.main.diary.services', ['emission.plugin.logger',
return getColoredStyle(baseDict, dh.getColor(feature.properties.sensed_mode));
};

var fmtTs = function(ts_in_secs, tz) {
return moment(ts_in_secs * 1000).tz(tz).format();
}

var printUserInput = function(ui) {
return fmtTs(ui.data.start_ts, ui.metadata.time_zone) + "("+ui.data.start_ts + ") -> "+
fmtTs(ui.data.end_ts, ui.metadata.time_zone) + "("+ui.data.end_ts + ")"+
" " + ui.data.label + " logged at "+ ui.metadata.write_ts;
}

dh.getUserInputForTrip = function(trip, nextTrip, userInputList) {
if (userInputList.length < 20) {
console.log("Input list = "+userInputList.map(printUserInput));
}
// undefined != true, so this covers the label view case as well
var isDraft = trip.isDraft == true;
var potentialCandidates = userInputList.filter(function(userInput) {
/*
console.log("startDelta "+userInput.data.label+
"= user("+fmtTs(userInput.data.start_ts, userInput.metadata.time_zone)+
") - trip("+fmtTs(userInput.data.start_ts, userInput.metadata.time_zone)+") = "+
(userInput.data.start_ts - trip.start_ts)+" should be positive");
console.log("endDelta = "+userInput.data.label+
"user("+fmtTs(userInput.data.end_ts, userInput.metadata.time_zone)+
") - trip("+fmtTs(trip.end_ts, userInput.metadata.time_zone)+") = "+
(userInput.data.end_ts - trip.end_ts)+" should be negative");
*/
// logic described in
// https://github.com/e-mission/e-mission-docs/issues/423
if (isDraft) {
if (userInputList.length < 20) {
var logStr = "Draft trip: comparing user = "+fmtTs(userInput.data.start_ts, userInput.metadata.time_zone)
+" -> "+fmtTs(userInput.data.end_ts, userInput.metadata.time_zone)
+" trip = "+fmtTs(trip.start_ts, userInput.metadata.time_zone)
+" -> "+fmtTs(trip.end_ts, userInput.metadata.time_zone)
+" checks are ("+(userInput.data.start_ts >= trip.start_ts)
+" && "+(userInput.data.start_ts <= trip.end_ts)
+" || "+(-(userInput.data.start_ts - trip.start_ts) <= 15 * 60)
+") && "+(userInput.data.end_ts <= trip.end_ts);
console.log(logStr);
// Logger.log(logStr);
}
return (userInput.data.start_ts >= trip.start_ts
&& userInput.data.start_ts <= trip.end_ts
|| -(userInput.data.start_ts - trip.start_ts) <= 15 * 60)
&& userInput.data.end_ts <= trip.end_ts;
} else {
// we know that the trip is cleaned so we can use the fmt_time
// but the confirm objects are not necessarily filled out
if (userInputList.length < 20) {
var logStr = "Cleaned trip: comparing user = "
+fmtTs(userInput.data.start_ts, userInput.metadata.time_zone)
+" -> "+fmtTs(userInput.data.end_ts, userInput.metadata.time_zone)
+" trip = "+trip.start_fmt_time
+" -> "+trip.end_fmt_time
+" start checks are "+(userInput.data.start_ts >= trip.start_ts)
+" && "+(userInput.data.start_ts <= trip.end_ts)
+" end checks are "+(userInput.data.end_ts <= trip.end_ts)
+" || "+((userInput.data.end_ts - trip.end_ts) <= 15 * 60)+")";
Logger.log(logStr);
}
// https://github.com/e-mission/e-mission-docs/issues/476#issuecomment-747222181
const startChecks = userInput.data.start_ts >= trip.start_ts &&
userInput.data.start_ts <= trip.end_ts;
var endChecks = (userInput.data.end_ts <= trip.end_ts ||
(userInput.data.end_ts - trip.end_ts) <= 15 * 60);
if (startChecks && !endChecks) {
if (angular.isDefined(nextTrip)) {
endChecks = userInput.data.end_ts <= nextTrip.start_ts;
Logger.log("Second level of end checks when the next trip is defined("+userInput.data.end_ts+" <= "+ nextTrip.start_ts+") = "+endChecks);
} else {
// next trip is not defined, last trip
endChecks = (userInput.data.end_local_dt.day == userInput.data.start_local_dt.day)
Logger.log("Second level of end checks for the last trip of the day");
Logger.log("compare "+userInput.data.end_local_dt.day + " with " + userInput.data.start_local_dt.day + " = " + endChecks);
}
if (endChecks) {
// If we have flipped the values, check to see that there
// is sufficient overlap
const overlapDuration = Math.min(userInput.data.end_ts, trip.end_ts) - Math.max(userInput.data.start_ts, trip.start_ts)
Logger.log("Flipped endCheck, overlap("+overlapDuration+
")/trip("+trip.duration+") = "+ (overlapDuration / trip.duration));
endChecks = (overlapDuration/trip.duration) > 0.5;
}
}
return startChecks && endChecks;
}
});
if (potentialCandidates.length === 0) {
if (userInputList.length < 20) {
Logger.log("In getUserInputForTripStartEnd, no potential candidates, returning []");
}
return undefined;
}

if (potentialCandidates.length === 1) {
Logger.log("In getUserInputForTripStartEnd, one potential candidate, returning "+ printUserInput(potentialCandidates[0]));
return potentialCandidates[0];
}

Logger.log("potentialCandidates are "+potentialCandidates.map(printUserInput));
var sortedPC = potentialCandidates.sort(function(pc1, pc2) {
return pc2.metadata.write_ts - pc1.metadata.write_ts;
});
var mostRecentEntry = sortedPC[0];
Logger.log("Returning mostRecentEntry "+printUserInput(mostRecentEntry));
return mostRecentEntry;
}


return dh;
})
.factory('Timeline', function(CommHelper, ConfirmHelper, $http, $ionicLoading, $window,
$rootScope, CommonGraph, UnifiedDataLoader, Logger, $translate) {
.factory('Timeline', function(CommHelper, SurveyOptions, $http, $ionicLoading, $window,
$rootScope, CommonGraph, UnifiedDataLoader, Logger, $injector, $translate) {
var timeline = {};
// corresponds to the old $scope.data. Contains all state for the current
// day, including the indication of the current day
timeline.data = {};
timeline.data.unifiedConfirmsResults = null;
timeline.UPDATE_DONE = "TIMELINE_UPDATE_DONE";

const surveyOpt = SurveyOptions.MULTILABEL;
const manualInputFactory = $injector.get(surveyOpt.service);

// Internal function, not publicly exposed
var getKeyForDate = function(date) {
var dateString = date.startOf('day').format('YYYY-MM-DD');
Expand All @@ -509,15 +402,13 @@ angular.module('emission.main.diary.services', ['emission.plugin.logger',
startTs: result.end_ts - 10,
endTs: moment().unix() + 10
}
var manualPromises = ConfirmHelper.INPUTS.map(function(inp) {
var manualPromises = manualInputFactory.MANUAL_KEYS.map(function(inp_key) {
return UnifiedDataLoader.getUnifiedMessagesForInterval(
ConfirmHelper.inputDetails[inp].key, pendingLabelQuery);
inp_key, pendingLabelQuery).then(manualInputFactory.extractResult);
});
const manualConfirmResults = {};
return Promise.all(manualPromises).then((manualResults) => {
const manualConfirmResults = {};
manualResults.forEach(function(mr, index) {
manualConfirmResults[ConfirmHelper.INPUTS[index]] = mr;
});
manualInputFactory.processManualInputs(manualResults, manualConfirmResults);
return [result, manualConfirmResults];
});
}).catch((err) => {
Expand Down Expand Up @@ -1097,18 +988,6 @@ angular.module('emission.main.diary.services', ['emission.plugin.logger',

var localCacheReadFn = timeline.updateFromDatabase;

var processManualInputs = function(manualResults) {
var mrString = 'unprocessed manual inputs '
+ manualResults.map(function(item, index) {
return ` ${item.length} ${ConfirmHelper.INPUTS[index]}`;
});
console.log(mrString);
timeline.data.unifiedConfirmsResults = {}
manualResults.forEach(function(mr, index) {
timeline.data.unifiedConfirmsResults[ConfirmHelper.INPUTS[index]] = mr;
});
}

var addUnprocessedTrips = function(processedTripList, day, completeStatus) {
var tripList = processedTripList;
if (!completeStatus) {
Expand All @@ -1126,13 +1005,15 @@ angular.module('emission.main.diary.services', ['emission.plugin.logger',
}

var readTripsAndUnprocessedInputs = function(day, tripReadFn, completeStatus, tq) {
var manualPromises = ConfirmHelper.INPUTS.map(function(inp) {
console.log("Reading values for list ", manualInputFactory.MANUAL_KEYS);
var manualPromises = manualInputFactory.MANUAL_KEYS.map(function(inp_key) {
return UnifiedDataLoader.getUnifiedMessagesForInterval(
ConfirmHelper.inputDetails[inp].key, tq);
inp_key, tq).then(manualInputFactory.extractResult);
});
let tripsReadPromise = tripReadFn(day);
// var surveyAnswersPromise = EnketoSurvey.getAllSurveyAnswers("manual/confirm_survey", { populateLabels: true });
let allManualPromise = Promise.all(manualPromises).then(processManualInputs);
timeline.data.unifiedConfirmsResults = {};
let allManualPromise = Promise.all(manualPromises).then((manualResults) =>
manualInputFactory.processManualInputs(manualResults, timeline.data.unifiedConfirmsResults));

let allTripsPromise = tripsReadPromise.then((processedTripList) => {
console.log("Reading trips from server finished successfully with length "
Expand Down
12 changes: 9 additions & 3 deletions www/js/intro.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

angular.module('emission.intro', ['emission.splash.startprefs',
'emission.splash.updatecheck',
'emission.survey.external.launch',
'emission.survey.enketo.demographics',
'emission.appstatus.permissioncheck',
'emission.i18n.utils',
'ionic-toast'])
Expand Down Expand Up @@ -30,7 +30,8 @@ angular.module('emission.intro', ['emission.splash.startprefs',
i18nUtils.geti18nFileName("templates/", "intro/summary", ".html"),
i18nUtils.geti18nFileName("templates/", "intro/consent", ".html"),
i18nUtils.geti18nFileName("templates/", "intro/sensor_explanation", ".html"),
i18nUtils.geti18nFileName("templates/", "intro/login", ".html")
i18nUtils.geti18nFileName("templates/", "intro/login", ".html"),
i18nUtils.geti18nFileName("templates/", "intro/survey", ".html")
]);
allIntroFiles.then(function(allIntroFilePaths) {
$scope.$apply(function() {
Expand All @@ -39,6 +40,7 @@ angular.module('emission.intro', ['emission.splash.startprefs',
$scope.consentFile = allIntroFilePaths[1];
$scope.explainFile = allIntroFilePaths[2];
$scope.loginFile = allIntroFilePaths[3];
$scope.surveyFile = allIntroFilePaths[4];
});
});

Expand Down Expand Up @@ -117,7 +119,7 @@ angular.module('emission.intro', ['emission.splash.startprefs',
client: retVal
});
});
$scope.finish();
$scope.next();
}, function(errorResult) {
$scope.alertError('User registration error', errorResult);
});
Expand Down Expand Up @@ -149,6 +151,10 @@ angular.module('emission.intro', ['emission.splash.startprefs',
StartPrefs.markIntroDone();
$scope.getIntroBox().slide(0);
StartPrefs.loadPreferredScreen();
// remove this view since the intro is done
// when we go back to the intro state, it will be recreated
$("[state='root.intro']").remove();
$scope.$destroy();
}

$ionicPlatform.ready().then(function() {
Expand Down
Loading

0 comments on commit e2f2143

Please sign in to comment.