feat: track downloads via MeasurementProtocol
This commit is contained in:
parent
5268985575
commit
acb7a81bc4
|
@ -5,7 +5,9 @@ from werkzeug.utils import import_string
|
||||||
|
|
||||||
import ckan.plugins.toolkit as tk
|
import ckan.plugins.toolkit as tk
|
||||||
|
|
||||||
|
CONFIG_TRACKING_ID = "googleanalytics.id"
|
||||||
CONFIG_HANDLER_PATH = "googleanalytics.download_handler"
|
CONFIG_HANDLER_PATH = "googleanalytics.download_handler"
|
||||||
|
CONFIG_TRACKING_MODE = "googleanalytics.tracking_mode"
|
||||||
|
|
||||||
DEFAULT_RESOURCE_URL_TAG = "/downloads/"
|
DEFAULT_RESOURCE_URL_TAG = "/downloads/"
|
||||||
DEFAULT_RECENT_VIEW_DAYS = 14
|
DEFAULT_RECENT_VIEW_DAYS = 14
|
||||||
|
@ -30,10 +32,9 @@ def download_handler():
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def tracking_mode():
|
def tracking_mode():
|
||||||
# type: () -> Literal["ga", "gtag", "gtm"]
|
# type: () -> Literal["ga", "gtag", "gtm"]
|
||||||
type_ = tk.config.get("googleanalytics.tracking_mode")
|
type_ = tk.config.get(CONFIG_TRACKING_MODE)
|
||||||
if type_:
|
if type_:
|
||||||
return type_
|
return type_
|
||||||
|
|
||||||
|
@ -75,6 +76,13 @@ def measurement_protocol_client_secret():
|
||||||
return tk.config.get("googleanalytics.measurement_protocol.client_secret")
|
return tk.config.get("googleanalytics.measurement_protocol.client_secret")
|
||||||
|
|
||||||
|
|
||||||
|
def measurement_protocol_track_downloads():
|
||||||
|
# type: () -> bool
|
||||||
|
return tk.asbool(
|
||||||
|
tk.config.get("googleanalytics.measurement_protocol.track_downloads")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def account():
|
def account():
|
||||||
return tk.config.get("googleanalytics.account")
|
return tk.config.get("googleanalytics.account")
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,9 @@ groups:
|
||||||
placeholder: UA-000000000-1
|
placeholder: UA-000000000-1
|
||||||
validators: not_empty
|
validators: not_empty
|
||||||
description: |
|
description: |
|
||||||
Google tag ID(`G-*`) for Google Analytics 4 properties or the
|
Google tag ID(`G-*`) for Google Analytics 4, the Tracking ID(`UA-*`)
|
||||||
Tracking ID(`UA-*`) for Universal Analytics properties.
|
for Universal Analytics, or container ID(`GTM-*`) for Google Tag
|
||||||
|
Manager.
|
||||||
|
|
||||||
- key: googleanalytics.download_handler
|
- key: googleanalytics.download_handler
|
||||||
default: ckan.views.resource:download
|
default: ckan.views.resource:download
|
||||||
|
@ -22,13 +23,14 @@ groups:
|
||||||
example: gtag
|
example: gtag
|
||||||
description: |
|
description: |
|
||||||
Defines the type of code snippet embedded into the page. Can be set
|
Defines the type of code snippet embedded into the page. Can be set
|
||||||
to either `ga`(enables `googleanalytics.js`) or `gtag`(enables
|
to `ga`(enables `googleanalytics.js`), `gtag`(enables `gtag.js`), or
|
||||||
`gtag.js`). The plugin will check `googleanalytics.id` and set
|
`gtm`(enables Google Tag Manager). The plugin will check
|
||||||
appropriate value depending on the prefix: `G-` enables `gtag`, while
|
`googleanalytics.id` and set appropriate value depending on the
|
||||||
`UA-` enables `ga` mode.
|
prefix: `G-` enables `gtag`, `UA-` enables `ga` mode, and `GTM-`
|
||||||
|
enables Google Tag Manager.
|
||||||
|
|
||||||
Use this option only if you know better which snippet you need. In
|
Use this option only if you know better which snippet you need. In
|
||||||
future, after dropping UniversalAnalytics, this option will be
|
future, after dropping UniversalAnalytics, this option may be
|
||||||
removed.
|
removed.
|
||||||
|
|
||||||
- key: googleanalytics.account
|
- key: googleanalytics.account
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import ckan.plugins.toolkit as tk
|
||||||
|
from ckanext.googleanalytics import config
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("with_plugins", "with_request_context")
|
||||||
|
class TestCodeSnippets:
|
||||||
|
@pytest.mark.parametrize("mode", ["ga", "gtag", "gtm"])
|
||||||
|
@pytest.mark.parametrize("tracking_id", ["UA-123", "G-123", "GTM-123"])
|
||||||
|
def test_tracking_(self, mode, tracking_id, app, ckan_config, monkeypatch):
|
||||||
|
snippet = tk.h.googleanalytics_header()
|
||||||
|
monkeypatch.setitem(ckan_config, config.CONFIG_TRACKING_ID, tracking_id)
|
||||||
|
monkeypatch.setitem(ckan_config, config.CONFIG_TRACKING_MODE, mode)
|
||||||
|
snippet = tk.render_snippet("googleanalytics/snippets/_{}.html".format(mode), {
|
||||||
|
"googleanalytics_id": tracking_id,
|
||||||
|
"googleanalytics_domain": config.domain(),
|
||||||
|
"googleanalytics_fields": config.fields(),
|
||||||
|
"googleanalytics_linked_domains": config.linked_domains()
|
||||||
|
})
|
||||||
|
resp = app.get("/")
|
||||||
|
assert snippet in resp.body
|
|
@ -9,19 +9,24 @@ from ckanext.googleanalytics import config
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
EVENT_API = "CKAN API Request"
|
EVENT_API = "CKAN API Request"
|
||||||
|
EVENT_DOWNLOAD = "CKAN Resource Download Request"
|
||||||
|
|
||||||
|
|
||||||
def send_event(data):
|
def send_event(data):
|
||||||
if isinstance(data, MeasurementProtocolData):
|
if isinstance(data, MeasurementProtocolData):
|
||||||
if data["event"] != EVENT_API:
|
if data["event"] == EVENT_API:
|
||||||
log.warning("Only API event supported by Measurement Protocol at the moment")
|
return _mp_api_handler({
|
||||||
return
|
"action": data["object"],
|
||||||
|
"payload": data["payload"],
|
||||||
|
})
|
||||||
|
|
||||||
return _mp_api_handler({
|
if data["event"] == EVENT_DOWNLOAD:
|
||||||
"action": data["object"],
|
return _mp_download_handler({"payload": {
|
||||||
"payload": data["payload"],
|
"resource_id": data["id"],
|
||||||
})
|
}})
|
||||||
|
|
||||||
|
log.warning("Only API and Download events supported by Measurement Protocol at the moment")
|
||||||
|
return
|
||||||
|
|
||||||
return _ga_handler(data)
|
return _ga_handler(data)
|
||||||
|
|
||||||
|
@ -32,11 +37,28 @@ class SafeJSONEncoder(json.JSONEncoder):
|
||||||
|
|
||||||
|
|
||||||
def _mp_api_handler(data_dict):
|
def _mp_api_handler(data_dict):
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"Sending API event to Google Analytics using the Measurement Protocol: %s",
|
"Sending API event to Google Analytics using the Measurement Protocol: %s",
|
||||||
data_dict
|
data_dict
|
||||||
)
|
)
|
||||||
|
_mp_event({
|
||||||
|
"name": data_dict["action"],
|
||||||
|
"params": data_dict["payload"]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _mp_download_handler(data_dict):
|
||||||
|
log.debug(
|
||||||
|
"Sending Downlaod event to Google Analytics using the Measurement Protocol: %s",
|
||||||
|
data_dict
|
||||||
|
)
|
||||||
|
_mp_event({
|
||||||
|
"name": "file_download",
|
||||||
|
"params": data_dict["payload"],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _mp_event(event):
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
"https://www.google-analytics.com/mp/collect",
|
"https://www.google-analytics.com/mp/collect",
|
||||||
params={
|
params={
|
||||||
|
@ -46,13 +68,10 @@ def _mp_api_handler(data_dict):
|
||||||
data=json.dumps({
|
data=json.dumps({
|
||||||
"client_id": config.measurement_protocol_client_id(),
|
"client_id": config.measurement_protocol_client_id(),
|
||||||
"non_personalized_ads": False,
|
"non_personalized_ads": False,
|
||||||
"events":[{
|
"events":[event]
|
||||||
"name": data_dict["action"],
|
|
||||||
"params": data_dict["payload"]
|
|
||||||
}]
|
|
||||||
}, cls=SafeJSONEncoder)
|
}, cls=SafeJSONEncoder)
|
||||||
)
|
)
|
||||||
# breakpoint()
|
|
||||||
if resp.status_code >= 300:
|
if resp.status_code >= 300:
|
||||||
log.error("Cannot post event: %s", resp)
|
log.error("Cannot post event: %s", resp)
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ def download(id, resource_id, filename=None, package_type="dataset"):
|
||||||
handler = resource.download
|
handler = resource.download
|
||||||
_post_analytics(
|
_post_analytics(
|
||||||
g.user,
|
g.user,
|
||||||
"CKAN Resource Download Request",
|
utils.EVENT_DOWNLOAD,
|
||||||
"Resource",
|
"Resource",
|
||||||
"Download",
|
"Download",
|
||||||
resource_id,
|
resource_id,
|
||||||
|
@ -90,7 +90,11 @@ def _post_analytics(
|
||||||
from ckanext.googleanalytics.plugin import GoogleAnalyticsPlugin
|
from ckanext.googleanalytics.plugin import GoogleAnalyticsPlugin
|
||||||
|
|
||||||
if config.tracking_id():
|
if config.tracking_id():
|
||||||
if config.measurement_protocol_client_id() and event_type == utils.EVENT_API:
|
mp_client_id = config.measurement_protocol_client_id()
|
||||||
|
if mp_client_id and (
|
||||||
|
event_type == utils.EVENT_API
|
||||||
|
or (event_type == utils.EVENT_DOWNLOAD and config.measurement_protocol_track_downloads())
|
||||||
|
):
|
||||||
data_dict = utils.MeasurementProtocolData({
|
data_dict = utils.MeasurementProtocolData({
|
||||||
"event": event_type,
|
"event": event_type,
|
||||||
"object": request_obj_type,
|
"object": request_obj_type,
|
||||||
|
@ -98,6 +102,7 @@ def _post_analytics(
|
||||||
"id": request_id,
|
"id": request_id,
|
||||||
"payload": request_payload,
|
"payload": request_payload,
|
||||||
})
|
})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
data_dict = utils.UniversalAnalyticsData({
|
data_dict = utils.UniversalAnalyticsData({
|
||||||
"v": 1,
|
"v": 1,
|
||||||
|
|
Loading…
Reference in New Issue