from urllib2_aws4auth import aws_urlopen, Request from urllib2 import HTTPError from urllib import urlencode import json import system import boto3 from pprint import pformat STAGE = 'prod' STAGE_CONFIG = { 'alpha':{ 'region':'us-east-1', 'endpoint': 'https://0wuzuy9rx3.execute-api.us-east-1.amazonaws.com/prod/user-activity-logger' }, 'beta': { 'region':'us-east-1', 'endpoint': 'https://33ymuc1b2a.execute-api.us-east-1.amazonaws.com/prod/user-activity-logger' }, 'prod': { 'region':'us-east-1', 'endpoint': 'https://ahikprejp4.execute-api.us-east-1.amazonaws.com/prod/user-activity-logger' } } REGION = STAGE_CONFIG.get(STAGE, 'alpha').get('region', 'us-east-1') SERVICE = 'execute-api' ENDPOINT = STAGE_CONFIG.get(STAGE, 'alpha').get('endpoint', 'https://0wuzuy9rx3.execute-api.us-east-1.amazonaws.com/prod/user-activity-logger') LOGGER = system.util.getLogger('productMetrics') def openSession(): CREDS = boto3.Session().get_credentials() AWS_ACCESS_KEY_ID = CREDS.access_key AWS_SECRET_ACCESS_KEY = CREDS.secret_key TOKEN = CREDS.token OPENER = aws_urlopen( AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, REGION, SERVICE, session_token=TOKEN, verify=False) return OPENER def formatPayload(payload): """Formats the payload based on what API expects to receive in the payload Also adds in some defaults (nulls) for the optional fields in case they are skipped from the payload Since defaults are not defined to required fields, will throw an error if they are not provided in the payload Args: payload (dict):all the fields that need to be sent to the API Returns: dict: dict formatted correctly for the API call """ data = { "project_id": "scada-activity-logger", #key used to log hashed user product metrics in backend, not the name of ignition project. "stage": payload.get("stage"), "plugin": "scada_activity_logger", "payload": { "username": payload.get("username"), "session_id": payload.get("session_id"), "site": payload.get("site"), "start_time": payload.get("start_time"), "end_time": payload.get("end_time", None), "user_UTCoffset": payload.get("user_UTCoffset", None), "resource_type": payload.get("resource_type"), "resource": payload.get("resource"), "current_page": payload.get("current_page"), "concurrent_users": payload.get("concurrent_users", getConcurrentUsers()) } } return data def getConcurrentUsers(): """Get the number of concurrent user session for the current project Returns: int: number of concurrent users """ project_name = system.project.getProjectName() concurrent_users = len(system.perspective.getSessionInfo()) return concurrent_users def createActivityPayload(viewObj, resource_type, resource = None, current_page= None): """Creates the activity payload for hashed user product metrics Typically called from view shutdown event script Uses the info from the view's custom param named activity_logger and session params to build the payload Args: viewObj: reference to the view (since custom param activity_logger is created on the view). If calling this function from a component pass self.view, if calling from view event scripts, pass self resource_type (string): page/session/click resource (string, optional): The resource that user interacted with. page name if interaction was with a page, feature/button name if it was a user click type interaction. Defaults to None. current_page (string, optional): Page name that the user is on when the interaction takes place. Defaults to None. Returns: dict: payload that needs to be formatted before sending to the API """ if viewObj.session.custom.product_metrics.enable and viewObj.session.props.device.type != 'designer': # since viewObj is a reference to the view, viewObj.session is reference to session session = viewObj.session stage = session.props.gateway.address user = session.props.auth.user.userName session_id = session.props.id site = session.custom.fc start_time = system.date.format(viewObj.custom.activityLogger.start_time, 'yyyy-MM-dd HH:mm:ss') end_time = None user_UTCoffset = session.props.device.timezone.utcOffset concurrent_users = getConcurrentUsers() if resource_type == 'page': end_time = system.date.format(system.date.now(), 'yyyy-MM-dd HH:mm:ss') try: pageid = viewObj.custom.activityLogger.pageid resource = pageid if resource == None else resource current_page = pageid if current_page == None else current_page except: pass if resource_type == 'click': start_time = system.date.format(system.date.now(), 'yyyy-MM-dd HH:mm:ss') end_time = None try: pageid = viewObj.custom.activityLogger.pageid # resource = pageid if resource == None else resource current_page = pageid if current_page == None else current_page except: pass payload = { "stage": stage, "username": user, "session_id": session_id, "site": site, "start_time": start_time, "end_time": end_time, "resource_type": resource_type, "resource": resource, "current_page": current_page, "user_UTCoffset": user_UTCoffset, "concurrent_users": concurrent_users } activityPayload = formatPayload(payload) return activityPayload else: return None def callActivityLoggerAPI(activityPayload): """Makes a post api call with the provided payload to log hashed user product metrics Handles converting the payload from dict to json Args: activityPayload (dict): Final payload that needs to be sent to the API Function will convert the payload to json """ if activityPayload: opener = openSession() params = activityPayload payload = json.dumps(params) method = 'POST' # in the headers the Ignition session username (session.props.auth.user.userName) must be supplied as 'X-Ignition-User' headers = { 'Content-type': 'application/json', } req = Request(url=ENDPOINT, method=method, data=payload, headers=headers) # open the request and process the read try: resp = opener(req) response = json.loads(resp.read()) error = None except HTTPError, e: error = str(e) response = None system.perspective.print(error) LOGGER.info(error) return {'error': error, 'response':response } def callLogger(viewObj, resource_type, resource = None, current_page= None): """Combines the createActivityPayload and callActivityLoggerAPI to log hashed user product metrics Typically called from view shutdown event script Uses the info from the view's custom param named activity_logger and session params to build the payload """ activityPayload = createActivityPayload(viewObj, resource_type, resource, current_page) if activityPayload: callActivityLoggerAPI(activityPayload) def testapi(): print STAGE user = 'ankikarw' opener = openSession() params = { "project_id": "scada-activity-logger", "stage": "api test alpha", "plugin": "scada_activity_logger", "payload": { "username": "user", "session_id": "1234567989", "site": "EWR4", "start_time": "2023-11-06 10:45:25", "end_time": "2023-11-06 10:57:18", "user_UTCoffset": "-8", "resource_type": "page", "resource": "home", "current_page": "home", "concurrent_users": "5" } } payload = json.dumps(params) method = 'POST' headers = { 'Content-type': 'application/json', } req = Request(url=ENDPOINT, method=method, data=payload, headers=headers) # open the request and process the read try: resp = opener(req) response = json.loads(resp.read()) error = None print response except HTTPError, e: error = str(e) response = None print error LOGGER.info(error) return {'error': error, 'response':response }