mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-08-14 02:26:58 -07:00
Add ga4mp library
* Remove UniversalAnalytics
This commit is contained in:
parent
ecb6d8b743
commit
42eeb90532
10 changed files with 983 additions and 546 deletions
3
lib/ga4mp/__init__.py
Normal file
3
lib/ga4mp/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from ga4mp.ga4mp import GtagMP, FirebaseMP
|
||||
|
||||
__all__ = ['GtagMP','FirebaseMP']
|
44
lib/ga4mp/event.py
Normal file
44
lib/ga4mp/event.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from ga4mp.item import Item
|
||||
|
||||
class Event(dict):
|
||||
def __init__(self, name):
|
||||
self.set_event_name(name)
|
||||
|
||||
def set_event_name(self, name):
|
||||
if len(name) > 40:
|
||||
raise ValueError("Event name cannot exceed 40 characters.")
|
||||
self["name"] = name
|
||||
|
||||
def get_event_name(self):
|
||||
return self.get("name")
|
||||
|
||||
def set_event_param(self, name, value):
|
||||
# Series of checks to comply with GA4 event collection limits: https://support.google.com/analytics/answer/9267744
|
||||
if len(name) > 40:
|
||||
raise ValueError("Event parameter name cannot exceed 40 characters.")
|
||||
if name in ["page_location", "page_referrer", "page_title"] and len(str(value)) > 300:
|
||||
raise ValueError("Event parameter value for page info cannot exceed 300 characters.")
|
||||
if name not in ["page_location", "page_referrer", "page_title"] and len(str(value)) > 100:
|
||||
raise ValueError("Event parameter value cannot exceed 100 characters.")
|
||||
if "params" not in self.keys():
|
||||
self["params"] = {}
|
||||
if len(self["params"]) >= 100:
|
||||
raise RuntimeError("Event cannot contain more than 100 parameters.")
|
||||
self["params"][name] = value
|
||||
|
||||
def get_event_params(self):
|
||||
return self.get("params")
|
||||
|
||||
def delete_event_param(self, name):
|
||||
# Since only 25 event parameters are allowed, this will allow the user to delete a parameter if necessary.
|
||||
self["params"].pop(name, None)
|
||||
|
||||
def create_new_item(self, item_id=None, item_name=None):
|
||||
return Item(item_id=item_id, item_name=item_name)
|
||||
|
||||
def add_item_to_event(self, item):
|
||||
if not isinstance(item, dict):
|
||||
raise ValueError("'item' must be an instance of a dictionary.")
|
||||
if "items" not in self["params"].keys():
|
||||
self.set_event_param("items", [])
|
||||
self["params"]["items"].append(item)
|
416
lib/ga4mp/ga4mp.py
Normal file
416
lib/ga4mp/ga4mp.py
Normal file
|
@ -0,0 +1,416 @@
|
|||
###############################################################################
|
||||
# Google Analytics 4 Measurement Protocol for Python
|
||||
# Copyright (c) 2022, Adswerve
|
||||
#
|
||||
# This project is free software, distributed under the BSD license.
|
||||
# Adswerve offers consulting and integration services if your firm needs
|
||||
# assistance in strategy, implementation, or auditing existing work.
|
||||
###############################################################################
|
||||
|
||||
import json
|
||||
import logging
|
||||
import urllib.request
|
||||
import time
|
||||
import datetime
|
||||
import random
|
||||
from ga4mp.utils import params_dict
|
||||
from ga4mp.event import Event
|
||||
from ga4mp.store import BaseStore, DictStore
|
||||
|
||||
import os, sys
|
||||
sys.path.append(
|
||||
os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
class BaseGa4mp(object):
|
||||
"""
|
||||
Parent class that provides an interface for sending data to Google Analytics, supporting the GA4 Measurement Protocol.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
api_secret : string
|
||||
Generated through the Google Analytics UI. To create a new secret, navigate in the Google Analytics UI to: Admin > Data Streams >
|
||||
[choose your stream] > Measurement Protocol API Secrets > Create
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
* Measurement Protocol (Google Analytics 4): https://developers.google.com/analytics/devguides/collection/protocol/ga4
|
||||
|
||||
Examples
|
||||
--------
|
||||
# Initialize tracking object for gtag usage
|
||||
>>> ga = gtagMP(api_secret = "API_SECRET", measurement_id = "MEASUREMENT_ID", client_id="CLIENT_ID")
|
||||
|
||||
# Initialize tracking object for Firebase usage
|
||||
>>> ga = firebaseMP(api_secret = "API_SECRET", firebase_app_id = "FIREBASE_APP_ID", app_instance_id="APP_INSTANCE_ID")
|
||||
|
||||
# Build an event
|
||||
>>> event_type = 'new_custom_event'
|
||||
>>> event_parameters = {'parameter_key_1': 'parameter_1', 'parameter_key_2': 'parameter_2'}
|
||||
>>> event = {'name': event_type, 'params': event_parameters }
|
||||
>>> events = [event]
|
||||
|
||||
# Send a custom event to GA4 immediately
|
||||
>>> ga.send(events)
|
||||
|
||||
# Postponed send of a custom event to GA4
|
||||
>>> ga.send(events, postpone=True)
|
||||
>>> ga.postponed_send()
|
||||
"""
|
||||
|
||||
def __init__(self, api_secret, store: BaseStore = None):
|
||||
self._initialization_time = time.time() # used for both session_id and calculating engagement time
|
||||
self.api_secret = api_secret
|
||||
self._event_list = []
|
||||
assert store is None or isinstance(store, BaseStore), "if supplied, store must be an instance of BaseStore"
|
||||
self.store = store or DictStore()
|
||||
self._check_store_requirements()
|
||||
self._base_domain = "https://www.google-analytics.com/mp/collect"
|
||||
self._validation_domain = "https://www.google-analytics.com/debug/mp/collect"
|
||||
|
||||
def _check_store_requirements(self):
|
||||
# Store must contain "session_id" and "last_interaction_time_msec" in order for tracking to work properly.
|
||||
if self.store.get_session_parameter("session_id") is None:
|
||||
self.store.set_session_parameter(name="session_id", value=int(self._initialization_time))
|
||||
# Note: "last_interaction_time_msec" factors into the required "engagement_time_msec" event parameter.
|
||||
self.store.set_session_parameter(name="last_interaction_time_msec", value=int(self._initialization_time * 1000))
|
||||
|
||||
def create_new_event(self, name):
|
||||
return Event(name=name)
|
||||
|
||||
def send(self, events, validation_hit=False, postpone=False, date=None):
|
||||
"""
|
||||
Method to send an http post request to google analytics with the specified events.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
events : List[Dict]
|
||||
A list of dictionaries of the events to be sent to Google Analytics. The list of dictionaries should adhere
|
||||
to the following format:
|
||||
|
||||
[{'name': 'level_end',
|
||||
'params' : {'level_name': 'First',
|
||||
'success': 'True'}
|
||||
},
|
||||
{'name': 'level_up',
|
||||
'params': {'character': 'John Madden',
|
||||
'level': 'First'}
|
||||
}]
|
||||
|
||||
validation_hit : bool, optional
|
||||
Boolean to depict if events should be tested against the Measurement Protocol Validation Server, by default False
|
||||
postpone : bool, optional
|
||||
Boolean to depict if provided event list should be postponed, by default False
|
||||
date : datetime
|
||||
Python datetime object for sending a historical event at the given date. Date cannot be in the future.
|
||||
"""
|
||||
|
||||
# check for any missing or invalid parameters among automatically collected and recommended event types
|
||||
self._check_params(events)
|
||||
self._check_date_not_in_future(date)
|
||||
self._add_session_id_and_engagement_time(events)
|
||||
|
||||
if postpone is True:
|
||||
# build event list to send later
|
||||
for event in events:
|
||||
event["_timestamp_micros"] = self._get_timestamp(time.time())
|
||||
self._event_list.append(event)
|
||||
else:
|
||||
# batch events into sets of 25 events, the maximum allowed.
|
||||
batched_event_list = [
|
||||
events[event : event + 25] for event in range(0, len(events), 25)
|
||||
]
|
||||
# send http post request
|
||||
self._http_post(
|
||||
batched_event_list, validation_hit=validation_hit, date=date
|
||||
)
|
||||
|
||||
def postponed_send(self):
|
||||
"""
|
||||
Method to send the events provided to Ga4mp.send(events,postpone=True)
|
||||
"""
|
||||
|
||||
for event in self._event_list:
|
||||
self._http_post([event], postpone=True)
|
||||
|
||||
# clear event_list for future use
|
||||
self._event_list = []
|
||||
|
||||
def append_event_to_params_dict(self, new_name_and_parameters):
|
||||
|
||||
"""
|
||||
Method to append event name and parameters key-value pairing(s) to parameters dictionary.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_name_and_parameters : Dict
|
||||
A dictionary with one key-value pair representing a new type of event to be sent to Google Analytics.
|
||||
The dictionary should adhere to the following format:
|
||||
|
||||
{'new_name': ['new_param_1', 'new_param_2', 'new_param_3']}
|
||||
"""
|
||||
|
||||
params_dict.update(new_name_and_parameters)
|
||||
|
||||
def _http_post(self, batched_event_list, validation_hit=False, postpone=False, date=None):
|
||||
"""
|
||||
Method to send http POST request to google-analytics.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
batched_event_list : List[List[Dict]]
|
||||
List of List of events. Places initial event payload into a list to send http POST in batches.
|
||||
validation_hit : bool, optional
|
||||
Boolean to depict if events should be tested against the Measurement Protocol Validation Server, by default False
|
||||
postpone : bool, optional
|
||||
Boolean to depict if provided event list should be postponed, by default False
|
||||
date : datetime
|
||||
Python datetime object for sending a historical event at the given date. Date cannot be in the future.
|
||||
Timestamp micros supports up to 48 hours of backdating.
|
||||
If date is specified, postpone must be False or an assertion will be thrown.
|
||||
"""
|
||||
self._check_date_not_in_future(date)
|
||||
status_code = None # Default set to know if batch loop does not work and to bound status_code
|
||||
|
||||
# set domain
|
||||
domain = self._base_domain
|
||||
if validation_hit is True:
|
||||
domain = self._validation_domain
|
||||
logger.info(f"Sending POST to: {domain}")
|
||||
|
||||
# loop through events in batches of 25
|
||||
batch_number = 1
|
||||
for batch in batched_event_list:
|
||||
# url and request slightly differ by subclass
|
||||
url = self._build_url(domain=domain)
|
||||
request = self._build_request(batch=batch)
|
||||
self._add_user_props_to_hit(request)
|
||||
|
||||
# make adjustments for postponed hit
|
||||
request["events"] = (
|
||||
{"name": batch["name"], "params": batch["params"]}
|
||||
if (postpone)
|
||||
else batch
|
||||
)
|
||||
|
||||
if date is not None:
|
||||
logger.info(f"Setting event timestamp to: {date}")
|
||||
assert (
|
||||
postpone is False
|
||||
), "Cannot send postponed historical hit, ensure postpone=False"
|
||||
|
||||
ts = self._datetime_to_timestamp(date)
|
||||
ts_micro = self._get_timestamp(ts)
|
||||
request["timestamp_micros"] = int(ts_micro)
|
||||
logger.info(f"Timestamp of request is: {request['timestamp_micros']}")
|
||||
|
||||
if postpone:
|
||||
# add timestamp to hit
|
||||
request["timestamp_micros"] = batch["_timestamp_micros"]
|
||||
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header("Content-Type", "application/json; charset=utf-8")
|
||||
jsondata = json.dumps(request)
|
||||
json_data_as_bytes = jsondata.encode("utf-8") # needs to be bytes
|
||||
req.add_header("Content-Length", len(json_data_as_bytes))
|
||||
result = urllib.request.urlopen(req, json_data_as_bytes)
|
||||
|
||||
status_code = result.status
|
||||
logger.info(f"Batch Number: {batch_number}")
|
||||
logger.info(f"Status code: {status_code}")
|
||||
batch_number += 1
|
||||
|
||||
return status_code
|
||||
|
||||
def _check_params(self, events):
|
||||
|
||||
"""
|
||||
Method to check whether the provided event payload parameters align with supported parameters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
events : List[Dict]
|
||||
A list of dictionaries of the events to be sent to Google Analytics. The list of dictionaries should adhere
|
||||
to the following format:
|
||||
|
||||
[{'name': 'level_end',
|
||||
'params' : {'level_name': 'First',
|
||||
'success': 'True'}
|
||||
},
|
||||
{'name': 'level_up',
|
||||
'params': {'character': 'John Madden',
|
||||
'level': 'First'}
|
||||
}]
|
||||
"""
|
||||
|
||||
# check to make sure it's a list of dictionaries with the right keys
|
||||
|
||||
assert type(events) == list, "events should be a list"
|
||||
|
||||
for event in events:
|
||||
|
||||
assert isinstance(event, dict), "each event should be an instance of a dictionary"
|
||||
|
||||
assert "name" in event, 'each event should have a "name" key'
|
||||
|
||||
assert "params" in event, 'each event should have a "params" key'
|
||||
|
||||
# check for any missing or invalid parameters
|
||||
|
||||
for e in events:
|
||||
event_name = e["name"]
|
||||
event_params = e["params"]
|
||||
if event_name in params_dict.keys():
|
||||
for parameter in params_dict[event_name]:
|
||||
if parameter not in event_params.keys():
|
||||
logger.warning(
|
||||
f"WARNING: Event parameters do not match event type.\nFor {event_name} event type, the correct parameter(s) are {params_dict[event_name]}.\nThe parameter '{parameter}' triggered this warning.\nFor a breakdown of currently supported event types and their parameters go here: https://support.google.com/analytics/answer/9267735\n"
|
||||
)
|
||||
|
||||
def _add_session_id_and_engagement_time(self, events):
|
||||
"""
|
||||
Method to add the session_id and engagement_time_msec parameter to all events.
|
||||
"""
|
||||
for event in events:
|
||||
current_time_in_milliseconds = int(time.time() * 1000)
|
||||
|
||||
event_params = event["params"]
|
||||
if "session_id" not in event_params.keys():
|
||||
event_params["session_id"] = self.store.get_session_parameter("session_id")
|
||||
if "engagement_time_msec" not in event_params.keys():
|
||||
last_interaction_time = self.store.get_session_parameter("last_interaction_time_msec")
|
||||
event_params["engagement_time_msec"] = current_time_in_milliseconds - last_interaction_time if current_time_in_milliseconds > last_interaction_time else 0
|
||||
self.store.set_session_parameter(name="last_interaction_time_msec", value=current_time_in_milliseconds)
|
||||
|
||||
def _add_user_props_to_hit(self, hit):
|
||||
|
||||
"""
|
||||
Method is a helper function to add user properties to outgoing hits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hit : dict
|
||||
"""
|
||||
|
||||
for key in self.store.get_all_user_properties():
|
||||
try:
|
||||
if key in ["user_id", "non_personalized_ads"]:
|
||||
hit.update({key: self.store.get_user_property(key)})
|
||||
else:
|
||||
if "user_properties" not in hit.keys():
|
||||
hit.update({"user_properties": {}})
|
||||
hit["user_properties"].update(
|
||||
{key: {"value": self.store.get_user_property(key)}}
|
||||
)
|
||||
except:
|
||||
logger.info(f"Failed to add user property to outgoing hit: {key}")
|
||||
|
||||
def _get_timestamp(self, timestamp):
|
||||
"""
|
||||
Method returns UNIX timestamp in microseconds for postponed hits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
"""
|
||||
return int(timestamp * 1e6)
|
||||
|
||||
def _datetime_to_timestamp(self, dt):
|
||||
"""
|
||||
Private method to convert a datetime object into a timestamp
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dt : datetime
|
||||
A datetime object in any format
|
||||
|
||||
Returns
|
||||
-------
|
||||
timestamp
|
||||
A UNIX timestamp in milliseconds
|
||||
"""
|
||||
return time.mktime(dt.timetuple())
|
||||
|
||||
def _check_date_not_in_future(self, date):
|
||||
"""
|
||||
Method to check that provided date is not in the future.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
date : datetime
|
||||
Python datetime object
|
||||
"""
|
||||
if date is None:
|
||||
pass
|
||||
else:
|
||||
assert (
|
||||
date <= datetime.datetime.now()
|
||||
), "Provided date cannot be in the future"
|
||||
|
||||
def _build_url(self, domain):
|
||||
raise NotImplementedError("Subclass should be using this function, but it was called through the base class instead.")
|
||||
|
||||
def _build_request(self, batch):
|
||||
raise NotImplementedError("Subclass should be using this function, but it was called through the base class instead.")
|
||||
|
||||
class GtagMP(BaseGa4mp):
|
||||
"""
|
||||
Subclass for users of gtag. See `Ga4mp` parent class for examples.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
measurement_id : string
|
||||
The identifier for a Data Stream. Found in the Google Analytics UI under: Admin > Data Streams > [choose your stream] > Measurement ID (top-right)
|
||||
client_id : string
|
||||
A unique identifier for a client, representing a specific browser/device.
|
||||
"""
|
||||
|
||||
def __init__(self, api_secret, measurement_id, client_id,):
|
||||
super().__init__(api_secret)
|
||||
self.measurement_id = measurement_id
|
||||
self.client_id = client_id
|
||||
|
||||
def _build_url(self, domain):
|
||||
return f"{domain}?measurement_id={self.measurement_id}&api_secret={self.api_secret}"
|
||||
|
||||
def _build_request(self, batch):
|
||||
return {"client_id": self.client_id, "events": batch}
|
||||
|
||||
def random_client_id(self):
|
||||
"""
|
||||
Utility function for generating a new client ID matching the typical format of 10 random digits and the UNIX timestamp in seconds, joined by a period.
|
||||
"""
|
||||
return "%0.10d" % random.randint(0,9999999999) + "." + str(int(time.time()))
|
||||
|
||||
class FirebaseMP(BaseGa4mp):
|
||||
"""
|
||||
Subclass for users of Firebase. See `Ga4mp` parent class for examples.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
firebase_app_id : string
|
||||
The identifier for a Firebase app. Found in the Firebase console under: Project Settings > General > Your Apps > App ID.
|
||||
app_instance_id : string
|
||||
A unique identifier for a Firebase app instance.
|
||||
* Android - getAppInstanceId() - https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics#public-taskstring-getappinstanceid
|
||||
* Kotlin - getAppInstanceId() - https://firebase.google.com/docs/reference/kotlin/com/google/firebase/analytics/FirebaseAnalytics#getappinstanceid
|
||||
* Swift - appInstanceID() - https://firebase.google.com/docs/reference/swift/firebaseanalytics/api/reference/Classes/Analytics#appinstanceid
|
||||
* Objective-C - appInstanceID - https://firebase.google.com/docs/reference/ios/firebaseanalytics/api/reference/Classes/FIRAnalytics#+appinstanceid
|
||||
* C++ - GetAnalyticsInstanceId() - https://firebase.google.com/docs/reference/cpp/namespace/firebase/analytics#getanalyticsinstanceid
|
||||
* Unity - GetAnalyticsInstanceIdAsync() - https://firebase.google.com/docs/reference/unity/class/firebase/analytics/firebase-analytics#getanalyticsinstanceidasync
|
||||
"""
|
||||
|
||||
def __init__(self, api_secret, firebase_app_id, app_instance_id):
|
||||
super().__init__(api_secret)
|
||||
self.firebase_app_id = firebase_app_id
|
||||
self.app_instance_id = app_instance_id
|
||||
|
||||
def _build_url(self, domain):
|
||||
return f"{domain}?firebase_app_id={self.firebase_app_id}&api_secret={self.api_secret}"
|
||||
|
||||
def _build_request(self, batch):
|
||||
return {"app_instance_id": self.app_instance_id, "events": batch}
|
11
lib/ga4mp/item.py
Normal file
11
lib/ga4mp/item.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
class Item(dict):
|
||||
def __init__(self, item_id=None, item_name=None):
|
||||
if item_id is None and item_name is None:
|
||||
raise ValueError("At least one of 'item_id' and 'item_name' is required.")
|
||||
if item_id is not None:
|
||||
self.set_parameter("item_id", str(item_id))
|
||||
if item_name is not None:
|
||||
self.set_parameter("item_name", item_name)
|
||||
|
||||
def set_parameter(self, name, value):
|
||||
self[name] = value
|
116
lib/ga4mp/store.py
Normal file
116
lib/ga4mp/store.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
class BaseStore(dict):
|
||||
def __init__(self):
|
||||
self.update([("user_properties", {}),("session_parameters", {})])
|
||||
|
||||
def save(self):
|
||||
raise NotImplementedError("Subclass should be using this function, but it was called through the base class instead.")
|
||||
|
||||
def _check_exists(self, key):
|
||||
# Helper function to make sure a key exists before trying to work with values within it.
|
||||
if key not in self.keys():
|
||||
self[key] = {}
|
||||
|
||||
def _set(self, param_type, name, value):
|
||||
# Helper function to set a single parameter (user or session or other).
|
||||
self._check_exists(key=param_type)
|
||||
self[param_type][name] = value
|
||||
|
||||
def _get_one(self, param_type, name):
|
||||
# Helper function to get a single parameter value (user or session).
|
||||
self._check_exists(key=param_type)
|
||||
return self[param_type].get(name, None)
|
||||
|
||||
def _get_all(self, param_type=None):
|
||||
# Helper function to get all user or session parameters - or the entire dictionary if not specified.
|
||||
if param_type is not None:
|
||||
return self[param_type]
|
||||
else:
|
||||
return self
|
||||
|
||||
# While redundant, the following make sure the distinction between session and user items is easier for the end user.
|
||||
def set_user_property(self, name, value):
|
||||
self._set(param_type="user_properties", name=name, value=value)
|
||||
|
||||
def get_user_property(self, name):
|
||||
return self._get_one(param_type="user_properties", name=name)
|
||||
|
||||
def get_all_user_properties(self):
|
||||
return self._get_all(param_type="user_properties")
|
||||
|
||||
def clear_user_properties(self):
|
||||
self["user_properties"] = {}
|
||||
|
||||
def set_session_parameter(self, name, value):
|
||||
self._set(param_type="session_parameters", name=name, value=value)
|
||||
|
||||
def get_session_parameter(self, name):
|
||||
return self._get_one(param_type="session_parameters", name=name)
|
||||
|
||||
def get_all_session_parameters(self):
|
||||
return self._get_all(param_type="session_parameters")
|
||||
|
||||
def clear_session_parameters(self):
|
||||
self["session_parameters"] = {}
|
||||
|
||||
# Similar functions for other items the user wants to store that don't fit the other two categories.
|
||||
def set_other_parameter(self, name, value):
|
||||
self._set(param_type="other", name=name, value=value)
|
||||
|
||||
def get_other_parameter(self, name):
|
||||
return self._get_one(param_type="other", name=name)
|
||||
|
||||
def get_all_other_parameters(self):
|
||||
return self._get_all(param_type="other")
|
||||
|
||||
def clear_other_parameters(self):
|
||||
self["other"] = {}
|
||||
|
||||
class DictStore(BaseStore):
|
||||
# Class for working with dictionaries that persist for the life of the class.
|
||||
def __init__(self, data: dict = None):
|
||||
super().__init__()
|
||||
if data:
|
||||
self.update(data)
|
||||
|
||||
def save(self):
|
||||
# Give the user back what's in the dictionary so they can decide how to save it.
|
||||
self._get_all()
|
||||
|
||||
class FileStore(BaseStore):
|
||||
# Class for working with dictionaries that get saved to a JSON file.
|
||||
def __init__(self, data_location: str = None):
|
||||
super().__init__()
|
||||
self.data_location = data_location
|
||||
try:
|
||||
self._load_file(data_location)
|
||||
except:
|
||||
logger.info(f"Failed to find file at location: {data_location}")
|
||||
|
||||
def _load_file(self):
|
||||
# Function to get data from the object's initialized location.
|
||||
# If the provided or stored data_location exists, read the file and overwrite the object's contents.
|
||||
if Path(self.data_location).exists():
|
||||
with open(self.data_location, "r") as json_file:
|
||||
self = json.load(json_file)
|
||||
# If the data_location doesn't exist, try to create a new starter JSON file at the location given.
|
||||
else:
|
||||
starter_dict = '{"user_properties":{}, "session_parameters":{}}'
|
||||
starter_json = json.loads(starter_dict)
|
||||
Path(self.data_location).touch()
|
||||
with open(self.data_location, "w") as json_file:
|
||||
json.dumps(starter_json, json_file)
|
||||
|
||||
def save(self):
|
||||
# Function to save the current dictionary to a JSON file at the object's initialized location.
|
||||
try:
|
||||
with open(self.data_location, "w") as outfile:
|
||||
json.dump(self, outfile)
|
||||
except:
|
||||
logger.info(f"Failed to save file at location: {self.data_location}")
|
392
lib/ga4mp/utils.py
Normal file
392
lib/ga4mp/utils.py
Normal file
|
@ -0,0 +1,392 @@
|
|||
# all automatically collected and recommended event types
|
||||
params_dict = {
|
||||
"ad_click": [
|
||||
"ad_event_id"
|
||||
],
|
||||
"ad_exposure": [
|
||||
"firebase_screen",
|
||||
"firebase_screen_id",
|
||||
"firebase_screen_class",
|
||||
"exposure_time",
|
||||
],
|
||||
"ad_impression": [
|
||||
"ad_event_id"
|
||||
],
|
||||
"ad_query": [
|
||||
"ad_event_id"
|
||||
],
|
||||
"ad_reward": [
|
||||
"ad_unit_id",
|
||||
"reward_type",
|
||||
"reward_value"
|
||||
],
|
||||
"add_payment_info": [
|
||||
"coupon",
|
||||
"currency",
|
||||
"items",
|
||||
"payment_type",
|
||||
"value"
|
||||
],
|
||||
"add_shipping_info": [
|
||||
"coupon",
|
||||
"currency",
|
||||
"items",
|
||||
"shipping_tier",
|
||||
"value"
|
||||
],
|
||||
"add_to_cart": [
|
||||
"currency",
|
||||
"items",
|
||||
"value"
|
||||
],
|
||||
"add_to_wishlist": [
|
||||
"currency",
|
||||
"items",
|
||||
"value"
|
||||
],
|
||||
"adunit_exposure": [
|
||||
"firebase_screen",
|
||||
"firebase_screen_id",
|
||||
"firebase_screen_class",
|
||||
"exposure_time",
|
||||
],
|
||||
"app_clear_data": [],
|
||||
"app_exception": [
|
||||
"fatal",
|
||||
"timestamp",
|
||||
"engagement_time_msec"
|
||||
],
|
||||
"app_remove": [],
|
||||
"app_store_refund": [
|
||||
"product_id",
|
||||
"value",
|
||||
"currency",
|
||||
"quantity"
|
||||
],
|
||||
"app_store_subscription_cancel": [
|
||||
"product_id",
|
||||
"price",
|
||||
"value",
|
||||
"currency",
|
||||
"cancellation_reason",
|
||||
],
|
||||
"app_store_subscription_convert": [
|
||||
"product_id",
|
||||
"price",
|
||||
"value",
|
||||
"currency",
|
||||
"quantity",
|
||||
],
|
||||
"app_store_subscription_renew": [
|
||||
"product_id",
|
||||
"price",
|
||||
"value",
|
||||
"currency",
|
||||
"quantity",
|
||||
"renewal_count",
|
||||
],
|
||||
"app_update": [
|
||||
"previous_app_version"
|
||||
],
|
||||
"begin_checkout": [
|
||||
"coupon",
|
||||
"currency",
|
||||
"items",
|
||||
"value"
|
||||
],
|
||||
"click": [],
|
||||
"dynamic_link_app_open": [
|
||||
"source",
|
||||
"medium",
|
||||
"campaign",
|
||||
"link_id",
|
||||
"accept_time"
|
||||
],
|
||||
"dynamic_link_app_update": [
|
||||
"source",
|
||||
"medium",
|
||||
"campaign",
|
||||
"link_id",
|
||||
"accept_time",
|
||||
],
|
||||
"dynamic_link_first_open": [
|
||||
"source",
|
||||
"medium",
|
||||
"campaign",
|
||||
"link_id",
|
||||
"accept_time",
|
||||
],
|
||||
"earn_virtual_currency": [
|
||||
"virtual_currency_name",
|
||||
"value"
|
||||
],
|
||||
"error": [
|
||||
"firebase_error",
|
||||
"firebase_error_value"
|
||||
],
|
||||
"file_download": [
|
||||
"file_extension",
|
||||
"file_name",
|
||||
"link_classes",
|
||||
"link_domain",
|
||||
"link_id",
|
||||
"link_text",
|
||||
"link_url",
|
||||
],
|
||||
"firebase_campaign": [
|
||||
"source",
|
||||
"medium",
|
||||
"campaign",
|
||||
"term",
|
||||
"content",
|
||||
"gclid",
|
||||
"aclid",
|
||||
"cp1",
|
||||
"anid",
|
||||
"click_timestamp",
|
||||
"campaign_info_source",
|
||||
],
|
||||
"firebase_in_app_message_action": [
|
||||
"message_name",
|
||||
"message_device_time",
|
||||
"message_id",
|
||||
],
|
||||
"firebase_in_app_message_dismiss": [
|
||||
"message_name",
|
||||
"message_device_time",
|
||||
"message_id",
|
||||
],
|
||||
"firebase_in_app_message_impression": [
|
||||
"message_name",
|
||||
"message_device_time",
|
||||
"message_id",
|
||||
],
|
||||
"first_open": [
|
||||
"previous_gmp_app_id",
|
||||
"updated_with_analytics",
|
||||
"previous_first_open_count",
|
||||
"system_app",
|
||||
"system_app_update",
|
||||
"deferred_analytics_collection",
|
||||
"reset_analytics_cause",
|
||||
"engagement_time_msec",
|
||||
],
|
||||
"first_visit": [],
|
||||
"generate_lead": [
|
||||
"value",
|
||||
"currency"
|
||||
],
|
||||
"in_app_purchase": [
|
||||
"product_id",
|
||||
"price",
|
||||
"value",
|
||||
"currency",
|
||||
"quantity",
|
||||
"subscription",
|
||||
"free_trial",
|
||||
"introductory_price",
|
||||
],
|
||||
"join_group": [
|
||||
"group_id"
|
||||
],
|
||||
"level_end": [
|
||||
"level_name",
|
||||
"success"
|
||||
],
|
||||
"level_start": [
|
||||
"level_name"
|
||||
],
|
||||
"level_up": [
|
||||
"character",
|
||||
"level"
|
||||
],
|
||||
"login": [
|
||||
"method"
|
||||
],
|
||||
"notification_dismiss": [
|
||||
"message_name",
|
||||
"message_time",
|
||||
"message_device_time",
|
||||
"message_id",
|
||||
"topic",
|
||||
"label",
|
||||
"message_channel",
|
||||
],
|
||||
"notification_foreground": [
|
||||
"message_name",
|
||||
"message_time",
|
||||
"message_device_time",
|
||||
"message_id",
|
||||
"topic",
|
||||
"label",
|
||||
"message_channel",
|
||||
"message_type",
|
||||
],
|
||||
"notification_open": [
|
||||
"message_name",
|
||||
"message_time",
|
||||
"message_device_time",
|
||||
"message_id",
|
||||
"topic",
|
||||
"label",
|
||||
"message_channel",
|
||||
],
|
||||
"notification_receive": [
|
||||
"message_name",
|
||||
"message_time",
|
||||
"message_device_time",
|
||||
"message_id",
|
||||
"topic",
|
||||
"label",
|
||||
"message_channel",
|
||||
"message_type",
|
||||
],
|
||||
"notification_send": [
|
||||
"message_name",
|
||||
"message_time",
|
||||
"message_device_time",
|
||||
"message_id",
|
||||
"topic",
|
||||
"label",
|
||||
"message_channel",
|
||||
],
|
||||
"os_update": [
|
||||
"previous_os_version"
|
||||
],
|
||||
"page_view": [
|
||||
"page_location",
|
||||
"page_referrer"
|
||||
],
|
||||
"post_score": [
|
||||
"level",
|
||||
"character",
|
||||
"score"
|
||||
],
|
||||
"purchase": [
|
||||
"affiliation",
|
||||
"coupon",
|
||||
"currency",
|
||||
"items",
|
||||
"transaction_id",
|
||||
"shipping",
|
||||
"tax",
|
||||
"value",
|
||||
],
|
||||
"refund": [
|
||||
"transaction_id",
|
||||
"value",
|
||||
"currency",
|
||||
"tax",
|
||||
"shipping",
|
||||
"items"
|
||||
],
|
||||
"remove_from_cart": [
|
||||
"currency",
|
||||
"items",
|
||||
"value"
|
||||
],
|
||||
"screen_view": [
|
||||
"firebase_screen",
|
||||
"firebase_screen_class",
|
||||
"firebase_screen_id",
|
||||
"firebase_previous_screen",
|
||||
"firebase_previous_class",
|
||||
"firebase_previous_id",
|
||||
"engagement_time_msec",
|
||||
],
|
||||
"scroll": [],
|
||||
"search": [
|
||||
"search_term"
|
||||
],
|
||||
"select_content": [
|
||||
"content_type",
|
||||
"item_id"
|
||||
],
|
||||
"select_item": [
|
||||
"items",
|
||||
"item_list_name",
|
||||
"item_list_id"
|
||||
],
|
||||
"select_promotion": [
|
||||
"items",
|
||||
"promotion_id",
|
||||
"promotion_name",
|
||||
"creative_name",
|
||||
"creative_slot",
|
||||
"location_id",
|
||||
],
|
||||
"session_start": [],
|
||||
"share": [
|
||||
"content_type",
|
||||
"item_id"
|
||||
],
|
||||
"sign_up": [
|
||||
"method"
|
||||
],
|
||||
"view_search_results": [
|
||||
"search_term"
|
||||
],
|
||||
"spend_virtual_currency": [
|
||||
"item_name",
|
||||
"virtual_currency_name",
|
||||
"value"
|
||||
],
|
||||
"tutorial_begin": [],
|
||||
"tutorial_complete": [],
|
||||
"unlock_achievement": [
|
||||
"achievement_id"
|
||||
],
|
||||
"user_engagement": [
|
||||
"engagement_time_msec"
|
||||
],
|
||||
"video_start": [
|
||||
"video_current_time",
|
||||
"video_duration",
|
||||
"video_percent",
|
||||
"video_provider",
|
||||
"video_title",
|
||||
"video_url",
|
||||
"visible",
|
||||
],
|
||||
"video_progress": [
|
||||
"video_current_time",
|
||||
"video_duration",
|
||||
"video_percent",
|
||||
"video_provider",
|
||||
"video_title",
|
||||
"video_url",
|
||||
"visible",
|
||||
],
|
||||
"video_complete": [
|
||||
"video_current_time",
|
||||
"video_duration",
|
||||
"video_percent",
|
||||
"video_provider",
|
||||
"video_title",
|
||||
"video_url",
|
||||
"visible",
|
||||
],
|
||||
"view_cart": [
|
||||
"currency",
|
||||
"items",
|
||||
"value"
|
||||
],
|
||||
"view_item": [
|
||||
"currency",
|
||||
"items",
|
||||
"value"
|
||||
],
|
||||
"view_item_list": [
|
||||
"items",
|
||||
"item_list_name",
|
||||
"item_list_id"
|
||||
],
|
||||
"view_promotion": [
|
||||
"items",
|
||||
"promotion_id",
|
||||
"promotion_name",
|
||||
"creative_name",
|
||||
"creative_slot",
|
||||
"location_id",
|
||||
],
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue