blob: 2f4ff13568c331004a16fa13fc6670660e51d369 [file] [log] [blame]
# Copyright 2016 The Charles Stark Draper Laboratory, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from userale.logger import Logger
from userale.version import __version__
from PyQt5.QtCore import QObject, QEvent
import datetime
class Ale (QObject):
"""
UserALE intercepts all application events by letting the developer install an event filter in their PyQT5 application to
generate detailed user logs. UserAle does not capture system level logs or events generated by a non-user
(a.k.a. system and signals sent between QObjects).
"""
def __init__(self,
url="",
autostart=True,
interval=5000,
user=None,
version=None,
details=False,
resolution=500,
shutoff=[]):
"""
:param url: [str] The URL to which logs will be sent (can either be file:// or http://)
:param autostart: [bool] Should UserAle start auotmatically on app rendering
:param interval: [int] The minimum time interval in ms betweeen batch transmission of logs
:param user: [str] Identifier for the user of the application
:param version: [str] The application version
:param details: [bool] Should detailed logs (key strokes, input/change values) be recorded
:param resolution: [int] Delay in ms between instances of high frequency logs like mouseovers, scrolls, etc
:param shutoff: [list] Turn off logging for specific events. For example, to ignore mousedown events, ['mousedown']
An example log will appear like this:
.. code-block:: python
{
'target': ,
'path': ['Example', 'testLineEdit'],
'clientTime': ,
'location': {'x': 82, 'y': 0},
'type': 'dragstart',
'userAction': 'true',
'details' : [ ],
'userId': 'userABC1234',
'toolVersion': 'myApplication',
'useraleVersion': '1.0.0 alpha'
}
"""
QObject.__init__(self)
# UserAle Configuration
self.url = url
self.autostart = autostart
self.interval = interval
self.user = user
self.version = version
self.details = details
self.resolution = resolution
# Store logs
self.logs = []
# Drag/Drop - track duration
self.dd = datetime.datetime.now ()
def eventFilter(self, object, event):
'''
:param object: [QObject] The object being watched.
:param event: [QEvent]
:return: [bool] Return true in order to filter the event out (stop it from being handled further). Otherwise return false.
Filters events for the watched object (in this case, QApplication)
'''
data = {}
if (event.type () == QEvent.MouseButtonPress):
data = self.handleMouseEvents ("mousedown", event, object)
elif (event.type () == QEvent.MouseButtonRelease):
data = self.handleMouseEvents ("mouseup", event, object)
elif (event.type () == QEvent.MouseMove):
data = self.handleMouseEvents ("mousemove", event, object)
elif (event.type () == QEvent.KeyPress):
data = self.handleKeyEvents ("keypress", event, object)
elif (event.type () == QEvent.KeyRelease):
data = self.handleKeyEvents ("keyrelease", event, object)
elif (event.type () == QEvent.Leave):
data = self.handleLeaveEvents ("keyrelease", event, object)
elif (event.type () == QEvent.Move):
data = self.handleMoveEvents ("keyrelease", event, object)
elif (event.type () == QEvent.Resize):
data = self.handleResizeEvents ("keyrelease", event, object)
elif (event.type () == QEvent.Scroll):
data = self.handleScrollEvents ("keyrelease", event, object)
elif (event.type () == QEvent.DragEnter):
data = self.handleDragEvents ("dragstart", event, object)
elif (event.type () == QEvent.DragLeave):
data = self.handleDragEvents ("dragleave", event, object)
elif (event.type () == QEvent.DragMove):
data = self.handleDragEvents ("dragmove", event, object)
elif (event.type () == QEvent.Drop):
data = self.handleDragEvents ("dragdrop", event, object)
else:
pass
if data:
Logger.stdout (data)
# return super(Ale, self).eventFilter(object, event)
return False
def getSelector (self, object):
"""
:param object: [QObject] The base class for all Qt objects.
:return: [str] The Qt object's name
Get target object's name (object defined by user or object's meta class name)
"""
return object.objectName() if object.objectName() else object.staticMetaObject.className()
def getLocation (self, event):
"""
:param event: [QEvent] The base class for all event classes.
:return: [dict] A dictionary representation of the x and y positions of the mouse cursor.
Grab the x and y position of the mouse cursor, relative to the widget that received the event.
"""
try:
return {"x" : event.pos ().x (), "y" : event.pos ().y ()}
except:
return None
def getPath (self, object):
"""
:param object: [QObject] The base class for all Qt objects.
:return: [list] List of QObjects up to the child object.
Fetch the entire path up the root of the tree for a leaf node object.
Recursive operation.
"""
if object.parent() is not None:
return self.getPath (object.parent()) + [self.getSelector (object)]
else:
return [self.getSelector (object)]
def getClientTime (self):
"""
:return: [str] String representation of the time the event was triggered.
Capture the time the event was captured.
"""
return str (datetime.datetime.now ())
def handleMouseEvents (self, event_type, event, object):
"""
:param event_type: [str] The string representation of the type of event being triggered by the user.
:param event: [QEvent] The base class for all event classes.
:param object: [QObject] The base class for all Qt objects.
:return: [dict] A userale log describing a mouse event.
Returns the userale log representing all mouse event data.
.. code-block:: python
"""
return self.__create_msg (event_type, event, object)
def handleKeyEvents (self, event_type, event, object):
"""
:param event_type: [str] The string representation of the type of event being triggered by the user.
:param event: [QEvent] The base class for all event classes.
:param object: [QObject] The base class for all Qt objects.
:return: [dict] A userale log describing a key event.
Returns the userale log representing all key events, including key name and key code.
"""
details = {'key' : event.text (), 'keycode' : event.key ()}
return self.__create_msg (event_type, event, object, details=details)
def handleDragEvents (self, event_type, event, object):
"""
:param event_type: [str] The string representation of the type of event being triggered by the user.
:param event: [QEvent] The base class for all event classes.
:param object: [QObject] The base class for all Qt objects.
:return: [dict] A userale log describing a drag event.
Returns the userale log representing all drag events.
"""
details = {}
if event_type == 'dragstart':
# start timer
self.dd = datetime.datetime.now ()
elif event_type == 'dragdrop' or event_type == 'dragleave':
details = {"elapsed" : str (datetime.datetime.now () - self.dd)}
self.dd = datetime.datetime.now ()
else:
# drag move event - ignore
pass
return self.__create_msg (event_type, event, object, details=details)
def handleMoveEvents (self, event_type, event, object):
"""
:param event_type: [str] The string representation of the type of event being triggered by the user.
:param event: [QEvent] The base class for all event classes.
:param object: [QObject] The base class for all Qt objects.
:return: [dict] A userale log describing a drag event.
Returns the userale log representing all move events.
"""
pass
def handleLeaveEvents (self, event_type, event, object):
"""
:param event_type: [str] The string representation of the type of event being triggered by the user.
:param event: [QEvent] The base class for all event classes.
:param object: [QObject] The base class for all Qt objects.
:return: [dict] A userale log describing a leave event.
Returns the userale log representing all leave events.
"""
pass
def handleResizeEvents (self, event_type, event, object):
"""
:param event_type: [str] The string representation of the type of event being triggered by the user.
:param event: [QEvent] The base class for all event classes.
:param object: [QObject] The base class for all Qt objects.
:return: [dict] A userale log describing a resize event.
Returns the userale log representing all resize events.
"""
pass
def handleScrollEvents (self, event_type, event, object):
"""
:param event_type: [str] The string representation of the type of event being triggered by the user.
:param event: [QEvent] The base class for all event classes.
:param object: [QObject] The base class for all Qt objects.
:return: [dict] A userale log describing a scroll event.
Returns the userale log representing all scroll events.
"""
pass
def __create_msg (self, event_type, event, object, details={}):
"""
Geneate UserAle log describing an event.
"""
data = {
'target': self.getSelector (object) ,
'path': self.getPath (object),
'clientTime': self.getClientTime (),
'location': self.getLocation(event),
'type': event_type ,
'userAction': 'true',
'details' : details,
'userId': self.user,
'toolVersion': self.version,
'useraleVersion': __version__
}
return data