Skip to content

Commit

Permalink
Add time spent mapping on user profile
Browse files Browse the repository at this point in the history
  • Loading branch information
sunidhiraheja committed Jul 18, 2018
1 parent a200850 commit 839cfe1
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 2 deletions.
14 changes: 14 additions & 0 deletions client/app/profile/profile.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
vm.username = '';
vm.currentlyLoggedInUser = null;
vm.userDetails = null;
vm.userStats = null;
vm.osmUserDetails = null;
vm.projects = [];
vm.map = null;
Expand Down Expand Up @@ -52,6 +53,7 @@
projectMapService.addPopupOverlay(hoverIdentify, clickIdentify);
getUserProjects();
getLevelSettings();
getUserStats();
}

/**
Expand Down Expand Up @@ -205,5 +207,17 @@
vm.mapperLevelAdvanced = data.mapperLevelAdvanced;
});
}

/**
* Get stats about the user
*/
function getUserStats() {
var resultsPromise = userService.getUserStats(vm.username);
resultsPromise.then(function (data) {
// On success, set the detailed stats for this user
vm.userStats = data;
});
}

}
})();
7 changes: 7 additions & 0 deletions client/app/profile/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ <h4>{{ profileCtrl.username }}'s {{ 'Progress' | translate }}</h4>
</div>
<hr/>
</div>
<div>
<h4>Stats</h4>
<span>Time Spent Mapping</span>
<span class="pull-right">{{ profileCtrl.userStats.timeSpentMapping | amDurationFormat : 'second' }}</span>
<hr/>
</div>
<div>
<h4>OSM details</h4>
<div ng-show="profileCtrl.osmUserDetails">
Expand All @@ -84,6 +90,7 @@ <h4>OSM details</h4>
{{ 'OSM heat map' | translate }}
</a>
</p>
<hr/>
</div>
</div>
<div class="roles pull-right">
Expand Down
25 changes: 25 additions & 0 deletions client/app/services/user.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
setLevel: setLevel,
getOSMUserDetails: getOSMUserDetails,
getUserProjects: getUserProjects,
getUserStats: getUserStats,
searchUser: searchUser,
searchAllUsers: searchAllUsers,
acceptLicense: acceptLicense,
Expand Down Expand Up @@ -117,6 +118,30 @@
})
}

/**
* Get detailed stats about the user
* @param username
* @returns {!jQuery.jqXHR|!jQuery.deferred|*|!jQuery.Promise}
*/
function getUserStats(username){
// Returns a promise
return $http({
method: 'GET',
url: configService.tmAPI + '/user/' + username + '/stats',
headers: {
'Content-Type': 'application/json; charset=UTF-8'
}
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
return response.data;
}, function errorCallback() {
// called asynchronously if an error occurs
// or server returns response with an error status.
return $q.reject("error");
})
}

/**
* Search a user
* @returns {!jQuery.jqXHR|*|!jQuery.deferred|!jQuery.Promise}
Expand Down
3 changes: 2 additions & 1 deletion server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def init_flask_restful_routes(app):
from server.api.stats_api import StatsContributionsAPI, StatsActivityAPI, StatsProjectAPI, HomePageStatsAPI
from server.api.tags_apis import CampaignsTagsAPI, OrganisationTagsAPI
from server.api.users.user_apis import UserAPI, UserOSMAPI, UserMappedProjects, UserSetRole, UserSetLevel,\
UserAcceptLicense, UserSearchFilterAPI, UserSearchAllAPI, UserUpdateAPI
UserAcceptLicense, UserSearchFilterAPI, UserSearchAllAPI, UserUpdateAPI, UserStatsAPI
from server.api.validator_apis import LockTasksForValidationAPI, UnlockTasksAfterValidationAPI, StopValidatingAPI,\
MappedTasksByUser
from server.api.grid.grid_apis import IntersectingTilesAPI
Expand Down Expand Up @@ -169,6 +169,7 @@ def init_flask_restful_routes(app):
api.add_resource(UserUpdateAPI, '/api/v1/user/update-details')
api.add_resource(UserMappedProjects, '/api/v1/user/<string:username>/mapped-projects')
api.add_resource(UserOSMAPI, '/api/v1/user/<string:username>/osm-details')
api.add_resource(UserStatsAPI, '/api/v1/user/<string:username>/stats')
api.add_resource(UserSetRole, '/api/v1/user/<string:username>/set-role/<string:role>')
api.add_resource(UserSetLevel, '/api/v1/user/<string:username>/set-level/<string:level>')
api.add_resource(UserAcceptLicense, '/api/v1/user/accept-license/<int:license_id>')
Expand Down
35 changes: 35 additions & 0 deletions server/api/users/user_apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,41 @@ def get(self, username):
return {"error": error_msg}, 500


class UserStatsAPI(Resource):
def get(self, username):
"""
Get detailed stats about user
---
tags:
- user
produces:
- application/json
parameters:
- name: username
in: path
description: The users username
required: true
type: string
default: Thinkwhere
responses:
200:
description: User found
404:
description: User not found
500:
description: Internal Server Error
"""
try:
stats_dto = UserService.get_detailed_stats(username)
return stats_dto.to_primitive(), 200
except NotFound:
return {"Error": "User not found"}, 404
except Exception as e:
error_msg = f'User GET - unhandled error: {str(e)}'
current_app.logger.critical(error_msg)
return {"error": error_msg}, 500


class UserMappedProjects(Resource):
def get(self, username):
"""
Expand Down
5 changes: 5 additions & 0 deletions server/models/dtos/user_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class UserDTO(Model):
linkedin_id = StringType(serialized_name='linkedinId')


class UserStatsDTO(Model):
""" DTO containing statistics about the user """
time_spent_mapping = IntType(serialized_name='timeSpentMapping')


class UserOSMDTO(Model):
""" DTO containing OSM details for the user """
account_created = StringType(required=True, serialized_name='accountCreated')
Expand Down
28 changes: 27 additions & 1 deletion server/services/users/user_service.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from cachetools import TTLCache, cached
from flask import current_app
from functools import reduce
import dateutil.parser
import datetime

from server.models.dtos.user_dto import UserDTO, UserOSMDTO, UserFilterDTO, UserSearchQuery, UserSearchDTO
from server.models.dtos.user_dto import UserDTO, UserOSMDTO, UserFilterDTO, UserSearchQuery, UserSearchDTO, \
UserStatsDTO
from server.models.postgis.task import TaskHistory
from server.models.postgis.user import User, UserRole, MappingLevel
from server.models.postgis.utils import NotFound
from server.services.users.osm_service import OSMService, OSMServiceError
Expand Down Expand Up @@ -81,6 +86,27 @@ def get_user_dto_by_username(requested_username: str, logged_in_user_id: int) ->

return requested_user.as_dto(logged_in_user.username)

@staticmethod
def get_detailed_stats(username: str):
user = UserService.get_user_by_username(username)
stats_dto = UserStatsDTO()

actions = TaskHistory.query.filter(
TaskHistory.user_id == user.id, TaskHistory.action == 'LOCKED_FOR_MAPPING'
).all()

total_time = datetime.datetime.min
for action in actions:
duration = dateutil.parser.parse(action.action_text)
total_time += datetime.timedelta(hours=duration.hour,
minutes=duration.minute,
seconds=duration.second,
microseconds=duration.microsecond)

stats_dto.time_spent_mapping = total_time.time().isoformat()

return stats_dto

@staticmethod
def update_user_details(user_id: int, user_dto: UserDTO) -> dict:
""" Update user with info supplied by user, if they add or change their email address a verification mail
Expand Down

0 comments on commit 839cfe1

Please sign in to comment.