# HG changeset patch # User ymh # Date 1467548167 -7200 # Node ID fa4fd5e8b54e8ad382c0f1e2d9c1722df6fb3840 # Parent f64fb2da5a5473e725178fe932ccbf65c4ee9470 add tracking of close + first version of tracking server side diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/static/metaeducation/js/mtdc-tracking-worker.js --- a/server/src/metaeducation/static/metaeducation/js/mtdc-tracking-worker.js Fri Jul 01 12:44:07 2016 +0200 +++ b/server/src/metaeducation/static/metaeducation/js/mtdc-tracking-worker.js Sun Jul 03 14:16:07 2016 +0200 @@ -12,7 +12,7 @@ var Mtdc = root.Mtdc; - Mtdc.TrackingWorker = function(currentUser, trackingUrl, registration, debounceDelay = 1000) { + Mtdc.TrackingWorker = function(renkan, trackingUrl, trackingCloseUrl, registration, debounceDelay = 1000) { function _sendTrackingInfo() { var trackingMessages = this.trackingMessages; @@ -37,8 +37,10 @@ var trackingWorker = { trackingMessages: [], - currentUser: currentUser, + currentUser: renkan.currentUser, + renkan: renkan, trackingUrl: trackingUrl, + trackingCloseUrl: trackingCloseUrl, getUUID4 : function() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, @@ -57,7 +59,7 @@ this.sendTrackingInfo(); }, flushTrackingInfo() { - this.sendTrackingInfo.flush(); + //this.sendTrackingInfo.flush(); }, _getBaseMsg: function(verb) { var timestamp = new Date(), @@ -182,7 +184,24 @@ updateView: function(viewData, changedData, previousData) { this._sendViewMsg('update', viewData, changedData, previousData); }, - closeProject: function(projData) { + closeProject: function() { + this.flushTrackingInfo(); + $.ajax({ + method: 'POST', + url: this.trackingCloseUrl, + headers: { + 'X-CSRFToken': this.csrftoken + }, + data: { + 'renkan_guid': this.renkan.project.get('id'), + 'registration': this.registration + } + + }).done(function() { + console.log('Send close tracking info success'); + }).fail(function(){ + console.log('send tracking data failed'); + }); } }; diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/templates/renkan_edit.html --- a/server/src/metaeducation/templates/renkan_edit.html Fri Jul 01 12:44:07 2016 +0200 +++ b/server/src/metaeducation/templates/renkan_edit.html Sun Jul 03 14:16:07 2016 +0200 @@ -50,7 +50,7 @@ }); _renkan.setCurrentUser('{{ user.external_id }}', '{{ user.username }}'); - var trackingWorker = Mtdc.TrackingWorker(_renkan.current_user, "{% url 'tracking_view' %}"); + var trackingWorker = Mtdc.TrackingWorker(_renkan, "{% url 'tracking_view' %}", "{% url 'tracking_view_close' %}"); Rkns.mtdcTracking(_renkan, trackingWorker); Rkns.mtdcJsonIO(_renkan, { @@ -58,6 +58,9 @@ user_id: '{{ user.external_id }}', user_name: '{{ user.username }}' }); + $(window).on('beforeunload', function(e) { + trackingWorker.closeProject(); + }); }; {% endblock js_import %} diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/tracking/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/src/metaeducation/tracking/__init__.py Sun Jul 03 14:16:07 2016 +0200 @@ -0,0 +1,4 @@ +from .tasks import send_tracking_data +from .messages import send_close_renkan + +__all__ = ['send_tracking_data', 'send_close_renkan'] diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/tracking/messages.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/src/metaeducation/tracking/messages.py Sun Jul 03 14:16:07 2016 +0200 @@ -0,0 +1,95 @@ +import datetime +import json +import logging +import pytz + +from .tasks import send_tracking_data + +logger = logging.getLogger(__name__) + + +def get_base_message(verb, renkan_id, current_user, registration = None): + #create + #open-read + #open-edit + #close + #delete + #update + timestamp = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) + + verbNode = { + "create" : { + "id": 'http://activitystrea.ms/schema/1.0/create', + "display": { 'fr-FR': 'a créé' } + }, + "update": { + "id": 'http://activitystrea.ms/schema/1.0/update', + "display": { 'fr-FR': 'a modifié' } + }, + "delete": { + "id": 'http://activitystrea.ms/schema/1.0/delete', + "display": { 'fr-FR': 'a supprimé' } + }, + "close": { + "id": 'http://activitystrea.ms/schema/1.0/close', + "display": { 'fr-FR': 'a fermé'} + }, + "open_read": { + "id": 'http://id.tincanapi.com/verb/viewed', + "display": { 'fr-FR': 'a vu'} + }, + "open_edit": { + "id" : 'http://activitystrea.ms/schema/1.0/access', + "display": { 'fr-FR': "a édité" } + } + }[verb]; + msg = { + 'actor': { + 'objectType': 'Agent', + 'name': current_user, + 'account': { + 'homePage': 'https://www.metaeducation.fr/Utilisateurs/', + 'name': current_user + } + }, + 'verb': verbNode, + 'object': { + 'objectType': 'Activity', + 'id': get_renkan_urn(renkan_id) + }, + 'context': { + 'extensions': { + 'http://liris.renkantracking.org/application': 'Outil carte mentale' + } + }, + 'timestamp': timestamp.isoformat() + }; + if registration: + msg['context']['registration'] = registration + + return msg + +def get_renkan_urn(renkan_id): + return 'urn:mtdc:renkan:renkan:%s' % renkan_id; + + + +def send_close_renkan(renkan, current_user, registration): + msg = get_base_message('close', renkan.renkan_guid, current_user, registration) + + msg['object'] = { + **(msg['object']), + **{ + "definition": { + "name": { + 'fr-FR': renkan.title + }, + "type": "http://www.w3.org/ns/activitystreams#Renkan", + "extensions": { + 'http://www.w3.org/ns/activitystreams#Data': json.loads(renkan.content), + } + } + } + } + + send_tracking_data(msg) diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/tracking/tasks.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/src/metaeducation/tracking/tasks.py Sun Jul 03 14:16:07 2016 +0200 @@ -0,0 +1,30 @@ +import logging, json +import requests +from django.conf import settings + +logger = logging.getLogger(__name__) + +def send_tracking_data(json_data): + + tracking_data = (json_data if isinstance(json_data, str) else json.dumps(json_data)).encode('utf-8') + + logger.debug("SENDING %r", tracking_data) + + try: + resp = requests.post( + settings.LRS_TRACKING_SERVICE_URL + "statements", + data=tracking_data, + auth=requests.auth.HTTPBasicAuth( + settings.LRS_MTDC_RENKAN_USERNAME, + settings.LRS_MTDC_RENKAN_PASSWORD + ), + headers = {"X-Experience-API-Version": "1.0.1", "Content-Type": "application/json"} + ) + logger.debug("tracking info sent %r: %r", resp.status_code, resp.text) + resp.raise_for_status() + except requests.exceptions.ConnectionError as e: + logger.exception("Tracking send error Connecting %r", e) + # do nothing this should be a fire and forget interface + except requests.exceptions.HTTPError as e: + logger.debug("Tracking send error posting data %r", e) + # do nothing this should be a fire and forget interface diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/urls.py --- a/server/src/metaeducation/urls.py Fri Jul 01 12:44:07 2016 +0200 +++ b/server/src/metaeducation/urls.py Sun Jul 03 14:16:07 2016 +0200 @@ -20,7 +20,7 @@ from django.core.urlresolvers import reverse_lazy from django.views.generic import RedirectView -from .views import ListRenkansView, NewRenkanView, EditRenkanView, ViewRenkanView, DeleteRenkanView, UITrackingView +from .views import ListRenkansView, NewRenkanView, EditRenkanView, ViewRenkanView, DeleteRenkanView, UITrackingView, UITrackingViewClose urlpatterns = [ @@ -33,7 +33,8 @@ url(r'^front/edit/(?P[\w-]+)/$', EditRenkanView.as_view(), name='front_edit_renkan'), url(r'^front/view/(?P[\w-]+)/$', ViewRenkanView.as_view(), name='front_view_renkan'), url(r'^tracking/$', UITrackingView.as_view(), name='tracking_view'), + url(r'^tracking/close/$', UITrackingViewClose.as_view(), name='tracking_view_close'), url(r'^front/delete/(?P[\w-]+)/$', staff_member_required(DeleteRenkanView.as_view()), name='front_delete_renkan') ] -urlpatterns += staticfiles_urlpatterns() \ No newline at end of file +urlpatterns += staticfiles_urlpatterns() diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/views.py --- a/server/src/metaeducation/views.py Fri Jul 01 12:44:07 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -import logging, json -from django.conf import settings -from django.core.urlresolvers import reverse -from django.http import HttpResponse, HttpResponseRedirect -from django.shortcuts import render, get_object_or_404 -from django.views.decorators.csrf import csrf_exempt -from django.views.generic import TemplateView, View -from renkanmanager.api.views import RenkanDetail -from renkanmanager.models import Renkan -from renkanmanager.serializers import RenkanSerializer -from metaeducation import __version__ -from metaeducation.utils import send_tracking_data - -logger = logging.getLogger(__name__) - -class ListRenkansView(View): - template_name = "renkan_list.html" - - def get(self, request): - renkans = Renkan.objects.filter(creator=request.user) - return render(request, self.template_name, {'renkans': renkans, 'version': __version__}) - - def post(self, request): - create_data = { - "title" : request.POST.get("title", "Untitled Renkan") - } - - serializer = RenkanSerializer(data=create_data) - if serializer.is_valid(): - serializer.save(creator=request.user) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', settings.BASE_URL)) - -class NewRenkanView(View): - - def get(self, request): - create_data = { - "title" : "Untitled Renkan" - } - serializer = RenkanSerializer(data=create_data) - if serializer.is_valid(): - new_renkan = serializer.save(creator=request.user) - return HttpResponseRedirect(reverse("front_edit_renkan", kwargs={"renkan_guid": new_renkan.renkan_guid})) - - -class ViewRenkanView(TemplateView): - template_name = "renkan_view.html" - - -class EditRenkanView(TemplateView): - template_name = "renkan_edit.html" - -class DeleteRenkanView(View): - - def get(self, request, renkan_guid): - request.method = "DELETE" - delete_response = RenkanDetail.as_view()(request, renkan_guid) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', settings.BASE_URL)) - -class UITrackingView(View): - def post(self, request): - try: - logger.debug("POSTING DATA %r", str(request.body, 'utf-8')) - # Testing if JSON is properly formatted - json.loads(str(request.body, 'utf-8')) - send_tracking_data(str(request.body, 'utf-8')) - return HttpResponse("Tracking data was sent") - except ValueError: - logger.debug("ERROR POSTING DATA") - return HttpResponse("Error sending data") \ No newline at end of file diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/views/__init__.py --- a/server/src/metaeducation/views/__init__.py Fri Jul 01 12:44:07 2016 +0200 +++ b/server/src/metaeducation/views/__init__.py Sun Jul 03 14:16:07 2016 +0200 @@ -1,5 +1,5 @@ from .renkan import ListRenkansView, NewRenkanView, EditRenkanView, ViewRenkanView, DeleteRenkanView -from .tracking import UITrackingView +from .tracking import UITrackingView, UITrackingViewClose -__all__ = [ "ListRenkansView", "NewRenkanView", "EditRenkanView", "ViewRenkanView", "DeleteRenkanView", "UITrackingView" ] +__all__ = [ "ListRenkansView", "NewRenkanView", "EditRenkanView", "ViewRenkanView", "DeleteRenkanView", "UITrackingView", "UITrackingViewClose" ] diff -r f64fb2da5a54 -r fa4fd5e8b54e server/src/metaeducation/views/tracking.py --- a/server/src/metaeducation/views/tracking.py Fri Jul 01 12:44:07 2016 +0200 +++ b/server/src/metaeducation/views/tracking.py Sun Jul 03 14:16:07 2016 +0200 @@ -1,42 +1,38 @@ -import logging, json -import requests +import logging from django.conf import settings -from django.http import HttpResponse, HttpResponseServerError +from django.http import HttpResponse from django.views.generic import View +from django import forms +from django.shortcuts import get_object_or_404 + +from metaeducation.tracking import send_tracking_data, send_close_renkan +from renkanmanager.models import Renkan logger = logging.getLogger(__name__) class UITrackingView(View): - def send_tracking_data(self, json_data): - tracking_data = json.loads(json_data) - logger.debug("SENDING %r", tracking_data) - resp = requests.post( - settings.LRS_TRACKING_SERVICE_URL + "statements", - json=tracking_data, - auth=requests.auth.HTTPBasicAuth( - settings.LRS_MTDC_RENKAN_USERNAME, - settings.LRS_MTDC_RENKAN_PASSWORD - ), - headers = {"X-Experience-API-Version": "1.0.1"} - ) - logger.debug("%r: %r", resp.status_code, resp.text) - return resp - def post(self, request): logger.debug("POSTING DATA %r", str(request.body, 'utf-8')) - try: - resp = self.send_tracking_data(str(request.body, 'utf-8')) - resp.raise_for_status() - except requests.exceptions.ConnectionError as e: - logger.debug("ERROR Connecting %r", e) - return HttpResponseServerError(str(e)) - except requests.exceptions.HTTPError as e: - logger.debug("ERROR POSTING DATA %r", e) - return HttpResponse(resp.text, status=resp.status_code, content_type='text/plain') + send_tracking_data(str(request.body, 'utf-8')) + + # This is a fire and forget !!! + # please track the log to see if everything is ok + return HttpResponse(status=204) + +class RenkanCloseForm(forms.Form): + renkan_guid = forms.UUIDField() + registration = forms.UUIDField() + + +class UITrackingViewClose(View): + def post(self, request): + renkan_close_form = RenkanCloseForm(request.POST) + if renkan_close_form.is_valid(): + form_data = renkan_close_form.cleaned_data + renkan = get_object_or_404(Renkan.objects.select_related('current_revision'), renkan_guid=form_data['renkan_guid']) + send_close_renkan(renkan, str(request.user.external_id), str(form_data['registration'])) + return HttpResponse(status=204) else: - httpresp = HttpResponse(resp.text, status=resp.status_code, content_type=resp.headers.get('content-type')) - for h in {'X-Experience-API-Consistent-Through', 'X-Experience-API-Version', 'Date'}.intersection(resp.headers.keys()): - httpresp[h] = resp.headers[h] - return httpresp + return HttpResponse(status=400)